From f9f6d85e8338672e8a8301b0dd0690665961cea0 Mon Sep 17 00:00:00 2001 From: lahirulakruwan Date: Thu, 3 Oct 2024 22:56:18 +0530 Subject: [PATCH 1/5] update person api changes added --- campus/bffs/enrollment/api/client.bal | 4 +- campus/bffs/enrollment/api/service.bal | 52 +++++++-------- campus/bffs/enrollment/api/types.bal | 10 ++- .../graphql_client/enrollment.graphql | 8 ++- .../graphql_client_cg_src/client.bal | 4 +- .../graphql_client_cg_src/types.bal | 4 ++ .../enrollment/graphql_client/schema.graphql | 3 + .../enrollment/graphql_client/schema.json | 63 +++++++++++++++++++ 8 files changed, 115 insertions(+), 33 deletions(-) diff --git a/campus/bffs/enrollment/api/client.bal b/campus/bffs/enrollment/api/client.bal index 16d5c3b..af8378f 100644 --- a/campus/bffs/enrollment/api/client.bal +++ b/campus/bffs/enrollment/api/client.bal @@ -40,8 +40,8 @@ public isolated client class GraphqlClient { json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetPersonByIdResponse); } - remote isolated function updatePerson(City permanent_address_city, Address mailing_address, Person person, Address permanent_address, City mailing_address_city) returns UpdatePersonResponse|graphql:ClientError { - string query = string `mutation updatePerson($person:Person!,$permanent_address:Address!,$permanent_address_city:City!,$mailing_address:Address!,$mailing_address_city:City!) {update_person(person:$person,permanent_address:$permanent_address,permanent_address_city:$permanent_address_city,mailing_address:$mailing_address,mailing_address_city:$mailing_address_city) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type {id name} notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch}}`; + remote isolated function updatePerson(Person person, City? permanent_address_city = (), Address? mailing_address = (), Address? permanent_address = (), City? mailing_address_city = ()) returns UpdatePersonResponse|graphql:ClientError { + string query = string `mutation updatePerson($person:Person!,$permanent_address:Address,$permanent_address_city:City,$mailing_address:Address,$mailing_address_city:City) {update_person(person:$person,permanent_address:$permanent_address,permanent_address_city:$permanent_address_city,mailing_address:$mailing_address,mailing_address_city:$mailing_address_city) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type {id name} notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; map variables = {"permanent_address_city": permanent_address_city, "mailing_address": mailing_address, "person": person, "permanent_address": permanent_address, "mailing_address_city": mailing_address_city}; json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, UpdatePersonResponse); diff --git a/campus/bffs/enrollment/api/service.bal b/campus/bffs/enrollment/api/service.bal index 318abff..d5f5ba2 100644 --- a/campus/bffs/enrollment/api/service.bal +++ b/campus/bffs/enrollment/api/service.bal @@ -78,69 +78,71 @@ service / on new http:Listener(9095) { resource function get districts() returns District[]|error { GetDistrictsResponse|graphql:ClientError getDistrictsResponse = globalDataClient->getDistricts(); - if(getDistrictsResponse is GetDistrictsResponse) { + if (getDistrictsResponse is GetDistrictsResponse) { District[] districtsData = []; foreach var district in getDistrictsResponse.districts { District|error districtData = district.cloneWithType(District); - if(districtData is District) { + if (districtData is District) { districtsData.push(districtData); } else { log:printError("Error while processing Application record received", districtData); - return error("Error while processing Application record received: " + districtData.message() + + return error("Error while processing Application record received: " + districtData.message() + ":: Detail: " + districtData.detail().toString()); } } return districtsData; - + } else { log:printError("Error while getting application", getDistrictsResponse); - return error("Error while getting application: " + getDistrictsResponse.message() + + return error("Error while getting application: " + getDistrictsResponse.message() + ":: Detail: " + getDistrictsResponse.detail().toString()); } } resource function get all_organizations() returns Organization[]|error { GetAllOrganizationsResponse|graphql:ClientError getAllOrganizationsResponse = globalDataClient->getAllOrganizations(); - if(getAllOrganizationsResponse is GetAllOrganizationsResponse) { + if (getAllOrganizationsResponse is GetAllOrganizationsResponse) { Organization[] organizationsData = []; foreach var organization in getAllOrganizationsResponse.all_organizations { Organization|error organizationData = organization.cloneWithType(Organization); - if(organizationData is Organization) { + if (organizationData is Organization) { organizationsData.push(organizationData); } else { log:printError("Error while processing Application record received", organizationData); - return error("Error while processing Application record received: " + organizationData.message() + + return error("Error while processing Application record received: " + organizationData.message() + ":: Detail: " + organizationData.detail().toString()); } } return organizationsData; - + } else { log:printError("Error while getting application", getAllOrganizationsResponse); - return error("Error while getting application: " + getAllOrganizationsResponse.message() + + return error("Error while getting application: " + getAllOrganizationsResponse.message() + ":: Detail: " + getAllOrganizationsResponse.detail().toString()); } } resource function put update_person(@http:Payload Person person) returns Person|error { - Address permanent_address =
person?.permanent_address; - Address mailing_address =
person?.mailing_address; - City permanent_address_city = permanent_address?.city; - City mailing_address_city = mailing_address?.city; - anydata remove_permanent_address_city = permanent_address.remove("city"); - anydata remove_mailing_address_city = mailing_address.remove("city"); - anydata remove_permanent_address = person.remove("permanent_address"); - anydata remove_mailing_address = person.remove("mailing_address"); - log:printDebug(remove_permanent_address.toString()); - log:printDebug(remove_mailing_address.toString()); - log:printDebug(remove_permanent_address_city.toString()); - log:printDebug(remove_mailing_address_city.toString()); - - - UpdatePersonResponse|graphql:ClientError updatePersonResponse = globalDataClient->updatePerson(permanent_address_city,mailing_address,person,permanent_address,mailing_address_city); + Address? permanent_address = person?.permanent_address; + Address? mailing_address = person?.mailing_address; + City? permanent_address_city = permanent_address?.city; + City? mailing_address_city = mailing_address?.city; + + if(permanent_address is Address){ + permanent_address.city = (); + } + + if(mailing_address is Address){ + mailing_address.city = (); + } + + person.permanent_address = (); + person.mailing_address = (); + + UpdatePersonResponse|graphql:ClientError updatePersonResponse = globalDataClient->updatePerson(person, permanent_address_city, mailing_address, permanent_address, mailing_address_city); if (updatePersonResponse is UpdatePersonResponse) { Person|error person_record = updatePersonResponse.update_person.cloneWithType(Person); if (person_record is Person) { diff --git a/campus/bffs/enrollment/api/types.bal b/campus/bffs/enrollment/api/types.bal index 8e6ea92..fbec93a 100644 --- a/campus/bffs/enrollment/api/types.bal +++ b/campus/bffs/enrollment/api/types.bal @@ -94,8 +94,11 @@ public type Person record { string? digital_id?; string? sex?; string? passport_no?; + string? current_job?; + int? created_by?; string? record_type?; Address? mailing_address?; + string? branch_code?; int[]? child_student?; string? bank_account_name?; int? avinya_phone?; @@ -103,13 +106,13 @@ public type Person record { string? nic_no?; int? phone?; int? organization_id?; + int? updated_by?; + string? academy_org_name?; string? asgardeo_id?; string? updated?; string? preferred_name?; string? jwt_sub_id?; int? academy_org_id?; - int? created_by?; - int? updated_by?; }; public type GetPersonsResponse record {| @@ -352,6 +355,9 @@ public type UpdatePersonResponse record {| string? bank_account_name; int? academy_org_id; string? bank_branch; + int? created_by; + int? updated_by; + string? current_job; |}? update_person; |}; diff --git a/campus/bffs/enrollment/graphql_client/enrollment.graphql b/campus/bffs/enrollment/graphql_client/enrollment.graphql index 9319678..f838a81 100644 --- a/campus/bffs/enrollment/graphql_client/enrollment.graphql +++ b/campus/bffs/enrollment/graphql_client/enrollment.graphql @@ -158,8 +158,9 @@ query getPersonById($id: Int!) { } } -mutation updatePerson($person: Person!,$permanent_address: Address!,$permanent_address_city: City!,$mailing_address: Address!,$mailing_address_city: City!) { - update_person(person: $person,permanent_address: $permanent_address,permanent_address_city: $permanent_address_city, +mutation updatePerson($person: Person!,$permanent_address: Address,$permanent_address_city: City, + $mailing_address: Address,$mailing_address_city: City) { + update_person(person: $person,permanent_address: $permanent_address,permanent_address_city: $permanent_address_city, mailing_address: $mailing_address,mailing_address_city: $mailing_address_city) { id preferred_name @@ -236,6 +237,9 @@ mutation updatePerson($person: Person!,$permanent_address: Address!,$permanent_a bank_account_name academy_org_id bank_branch + created_by + updated_by + current_job } } diff --git a/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/client.bal b/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/client.bal index 0da245c..e069424 100644 --- a/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/client.bal +++ b/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/client.bal @@ -39,8 +39,8 @@ public isolated client class GraphqlClient { json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetPersonByIdResponse); } - remote isolated function updatePerson(City permanent_address_city, Address mailing_address, Person person, Address permanent_address, City mailing_address_city) returns UpdatePersonResponse|graphql:ClientError { - string query = string `mutation updatePerson($person:Person!,$permanent_address:Address!,$permanent_address_city:City!,$mailing_address:Address!,$mailing_address_city:City!) {update_person(person:$person,permanent_address:$permanent_address,permanent_address_city:$permanent_address_city,mailing_address:$mailing_address,mailing_address_city:$mailing_address_city) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type {id name} notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch}}`; + remote isolated function updatePerson(Person person, City? permanent_address_city = (), Address? mailing_address = (), Address? permanent_address = (), City? mailing_address_city = ()) returns UpdatePersonResponse|graphql:ClientError { + string query = string `mutation updatePerson($person:Person!,$permanent_address:Address,$permanent_address_city:City,$mailing_address:Address,$mailing_address_city:City) {update_person(person:$person,permanent_address:$permanent_address,permanent_address_city:$permanent_address_city,mailing_address:$mailing_address,mailing_address_city:$mailing_address_city) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type {id name} notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; map variables = {"permanent_address_city": permanent_address_city, "mailing_address": mailing_address, "person": person, "permanent_address": permanent_address, "mailing_address_city": mailing_address_city}; json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, UpdatePersonResponse); diff --git a/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/types.bal b/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/types.bal index 9627c2c..eb52437 100644 --- a/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/types.bal +++ b/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/types.bal @@ -310,6 +310,7 @@ public type Person record { string? digital_id?; string? sex?; string? passport_no?; + string? current_job?; int? created_by?; string? record_type?; Address? mailing_address?; @@ -666,6 +667,9 @@ public type UpdatePersonResponse record {| string? bank_account_name; int? academy_org_id; string? bank_branch; + int? created_by; + int? updated_by; + string? current_job; |}? update_person; |}; diff --git a/campus/bffs/enrollment/graphql_client/schema.graphql b/campus/bffs/enrollment/graphql_client/schema.graphql index 06786dd..b3f3ef8 100644 --- a/campus/bffs/enrollment/graphql_client/schema.graphql +++ b/campus/bffs/enrollment/graphql_client/schema.graphql @@ -666,6 +666,7 @@ type Mutation { update_consumable_replenishment(inventories: [Inventory!]!): [InventoryData!] update_consumable_depletion(inventories: [Inventory!]!): [InventoryData!] update_person(person: Person!, permanent_address: Address, permanent_address_city: City, mailing_address: Address, mailing_address_city: City): PersonData + insert_person(person: Person!, mailing_address: Address, mailing_address_city: City): PersonData } input Organization { @@ -747,6 +748,7 @@ input Person { academy_org_id: Int academy_org_name: String branch_code: String + current_job: String created_by: Int updated_by: Int } @@ -785,6 +787,7 @@ type PersonData { academy_org_id: Int organization_id: Int branch_code: String + current_job: String created_by: Int updated_by: Int } diff --git a/campus/bffs/enrollment/graphql_client/schema.json b/campus/bffs/enrollment/graphql_client/schema.json index eb62c48..29facc7 100644 --- a/campus/bffs/enrollment/graphql_client/schema.json +++ b/campus/bffs/enrollment/graphql_client/schema.json @@ -391,6 +391,17 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "current_job", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "created_by", "args": [], @@ -8670,6 +8681,49 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "insert_person", + "args": [ + { + "name": "person", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "Person", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "mailing_address", + "type": { + "kind": "INPUT_OBJECT", + "name": "Address", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "mailing_address_city", + "type": { + "kind": "INPUT_OBJECT", + "name": "City", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "PersonData", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -9180,6 +9234,15 @@ }, "defaultValue": null }, + { + "name": "current_job", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, { "name": "created_by", "type": { From 1596b4eb1d2c50df45726cfe774080afaea83ffa Mon Sep 17 00:00:00 2001 From: YujithIsura Date: Fri, 4 Oct 2024 10:07:24 +0530 Subject: [PATCH 2/5] lint --- .../avinya/enrollment/lib/widgets/students.dart | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart index 7ab8dae..c3232f8 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart @@ -58,10 +58,18 @@ class _StudentsState extends State { return await fetchOrganizationsByAvinyaType(86); } - Future> fetchAvinyaTypes(newValue) async { - if (newValue != null) { - if (DateTime.parse(newValue.organization_metadata[1].value.toString()) - .isBefore(DateTime.parse('2024-03-01'))) { + Future> fetchAvinyaTypes(dynamic newValue) async { + List filteredAvinyaTypeIdValues; + + // Check if newValue is not null and contains valid metadata + if (newValue != null && + newValue.organization_metadata != null && + newValue.organization_metadata.isNotEmpty) { + // Parse the metadata value to a DateTime and perform date range checks + DateTime metadataDate = + DateTime.parse(newValue.organization_metadata[1].value.toString()); + + if (newValue.organization_metadata[1].value.toString() == '2024-07-26') { filteredAvinyaTypeIdValues = [ AvinyaTypeId.Empower, AvinyaTypeId.IT, @@ -75,6 +83,7 @@ class _StudentsState extends State { ]; } } else { + // Default value if newValue is null or invalid filteredAvinyaTypeIdValues = [ AvinyaTypeId.Empower, AvinyaTypeId.FutureEnrollees From 37fb998fa1a3d61ef14ff1a68e74a7dbcee790bd Mon Sep 17 00:00:00 2001 From: lahirulakruwan Date: Fri, 4 Oct 2024 19:08:28 +0530 Subject: [PATCH 3/5] Add person api changes added --- campus/bffs/enrollment/api/client.bal | 13 ++- campus/bffs/enrollment/api/service.bal | 27 +++++ campus/bffs/enrollment/api/types.bal | 102 ++++++++++++++-- .../graphql_client/enrollment.graphql | 109 +++++++++++++++--- .../graphql_client_cg_src/client.bal | 12 +- .../graphql_client_cg_src/types.bal | 102 ++++++++++++++-- 6 files changed, 319 insertions(+), 46 deletions(-) diff --git a/campus/bffs/enrollment/api/client.bal b/campus/bffs/enrollment/api/client.bal index af8378f..e93a818 100644 --- a/campus/bffs/enrollment/api/client.bal +++ b/campus/bffs/enrollment/api/client.bal @@ -29,19 +29,19 @@ public isolated client class GraphqlClient { } remote isolated function getPersons(int organization_id, int avinya_type_id) returns GetPersonsResponse|graphql:ClientError { - string query = string `query getPersons($organization_id:Int!,$avinya_type_id:Int!) {persons(organization_id:$organization_id,avinya_type_id:$avinya_type_id) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type {id name} notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch}}`; + string query = string `query getPersons($organization_id:Int!,$avinya_type_id:Int!) {persons(organization_id:$organization_id,avinya_type_id:$avinya_type_id) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type_id notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; map variables = {"organization_id": organization_id, "avinya_type_id": avinya_type_id}; json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetPersonsResponse); } remote isolated function getPersonById(int id) returns GetPersonByIdResponse|graphql:ClientError { - string query = string `query getPersonById($id:Int!) {person_by_id(id:$id) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type {id name} notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch}}`; + string query = string `query getPersonById($id:Int!) {person_by_id(id:$id) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type_id notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; map variables = {"id": id}; json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetPersonByIdResponse); } remote isolated function updatePerson(Person person, City? permanent_address_city = (), Address? mailing_address = (), Address? permanent_address = (), City? mailing_address_city = ()) returns UpdatePersonResponse|graphql:ClientError { - string query = string `mutation updatePerson($person:Person!,$permanent_address:Address,$permanent_address_city:City,$mailing_address:Address,$mailing_address_city:City) {update_person(person:$person,permanent_address:$permanent_address,permanent_address_city:$permanent_address_city,mailing_address:$mailing_address,mailing_address_city:$mailing_address_city) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type {id name} notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; + string query = string `mutation updatePerson($person:Person!,$permanent_address:Address,$permanent_address_city:City,$mailing_address:Address,$mailing_address_city:City) {update_person(person:$person,permanent_address:$permanent_address,permanent_address_city:$permanent_address_city,mailing_address:$mailing_address,mailing_address_city:$mailing_address_city) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type_id notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; map variables = {"permanent_address_city": permanent_address_city, "mailing_address": mailing_address, "person": person, "permanent_address": permanent_address, "mailing_address_city": mailing_address_city}; json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, UpdatePersonResponse); @@ -58,5 +58,10 @@ public isolated client class GraphqlClient { json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetAllOrganizationsResponse); } - + remote isolated function insertPerson(Person person, Address? mailing_address = (), City? mailing_address_city = ()) returns InsertPersonResponse|graphql:ClientError { + string query = string `mutation insertPerson($person:Person!,$mailing_address:Address,$mailing_address_city:City) {insert_person(person:$person,mailing_address:$mailing_address,mailing_address_city:$mailing_address_city) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type_id notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; + map variables = {"mailing_address": mailing_address, "person": person, "mailing_address_city": mailing_address_city}; + json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); + return check performDataBinding(graphqlResponse, InsertPersonResponse); + } } diff --git a/campus/bffs/enrollment/api/service.bal b/campus/bffs/enrollment/api/service.bal index d5f5ba2..00b8494 100644 --- a/campus/bffs/enrollment/api/service.bal +++ b/campus/bffs/enrollment/api/service.bal @@ -159,4 +159,31 @@ service / on new http:Listener(9095) { } } + resource function post add_person(@http:Payload Person person) returns Person|error { + + Address? mailing_address = person?.mailing_address; + City? mailing_address_city = mailing_address?.city; + + if(mailing_address is Address){ + mailing_address.city = (); + } + + person.mailing_address = (); + + InsertPersonResponse|graphql:ClientError addPersonResponse = globalDataClient->insertPerson(person,mailing_address,mailing_address_city); + if (addPersonResponse is InsertPersonResponse) { + Person|error person_record = addPersonResponse.insert_person.cloneWithType(Person); + if (person_record is Person) { + return person_record; + } else { + log:printError("Error while processing Application record received", person_record); + return error("Error while processing Application record received: " + person_record.message() + + ":: Detail: " + person_record.detail().toString()); + } + } else { + log:printError("Error while creating application", addPersonResponse); + return error("Error while creating application: " + addPersonResponse.message() + + ":: Detail: " + addPersonResponse.detail().toString()); + } + } } diff --git a/campus/bffs/enrollment/api/types.bal b/campus/bffs/enrollment/api/types.bal index fbec93a..f1ab410 100644 --- a/campus/bffs/enrollment/api/types.bal +++ b/campus/bffs/enrollment/api/types.bal @@ -176,10 +176,7 @@ public type GetPersonsResponse record {| |} name; |}[]? parent_organizations; |}? organization; - record {| - int? id; - string? name; - |}? avinya_type; + int? avinya_type_id; string? notes; string? nic_no; string? passport_no; @@ -193,6 +190,9 @@ public type GetPersonsResponse record {| string? bank_account_name; int? academy_org_id; string? bank_branch; + int? created_by; + int? updated_by; + string? current_job; |}[] persons; |}; @@ -257,10 +257,7 @@ public type GetPersonByIdResponse record {| |} name; |}[]? parent_organizations; |}? organization; - record {| - int? id; - string? name; - |}? avinya_type; + int? avinya_type_id; string? notes; string? nic_no; string? passport_no; @@ -274,6 +271,9 @@ public type GetPersonByIdResponse record {| string? bank_account_name; int? academy_org_id; string? bank_branch; + int? created_by; + int? updated_by; + string? current_job; |}? person_by_id; |}; @@ -338,10 +338,7 @@ public type UpdatePersonResponse record {| |} name; |}[]? parent_organizations; |}? organization; - record {| - int? id; - string? name; - |}? avinya_type; + int? avinya_type_id; string? notes; string? nic_no; string? passport_no; @@ -416,3 +413,84 @@ public type GetAllOrganizationsResponse record {| string? notes; |}[] all_organizations; |}; + +public type InsertPersonResponse record {| + map __extensions?; + record {| + int? id; + string? preferred_name; + string? full_name; + string? date_of_birth; + string? sex; + string? asgardeo_id; + string? jwt_sub_id; + string? created; + string? updated; + string? jwt_email; + record {| + record {| + int? id; + record {| + string? name_en; + string? name_si; + string? name_ta; + |} name; + |} city; + string? street_address; + int? phone; + int? id; + |}? permanent_address; + record {| + record {| + int? id; + record {| + string? name_en; + string? name_si; + string? name_ta; + |} name; + |} city; + string? street_address; + int? phone; + int? id; + |}? mailing_address; + int? phone; + record {| + int? id; + string? description; + string? notes; + record {| + int? id; + |}? address; + record {| + int? id; + string? name; + |}? avinya_type; + record {| + string? name_en; + |} name; + record {| + int? id; + record {| + string? name_en; + |} name; + |}[]? parent_organizations; + |}? organization; + int? avinya_type_id; + string? notes; + string? nic_no; + string? passport_no; + string? id_no; + string? email; + string? street_address; + string? digital_id; + int? avinya_phone; + string? bank_name; + string? bank_account_number; + string? bank_account_name; + int? academy_org_id; + string? bank_branch; + int? created_by; + int? updated_by; + string? current_job; + |}? insert_person; +|}; diff --git a/campus/bffs/enrollment/graphql_client/enrollment.graphql b/campus/bffs/enrollment/graphql_client/enrollment.graphql index f838a81..60a7e83 100644 --- a/campus/bffs/enrollment/graphql_client/enrollment.graphql +++ b/campus/bffs/enrollment/graphql_client/enrollment.graphql @@ -58,10 +58,7 @@ query getPersons($organization_id: Int!, $avinya_type_id: Int!) { } } } - avinya_type { - id - name - } + avinya_type_id notes nic_no passport_no @@ -75,6 +72,9 @@ query getPersons($organization_id: Int!, $avinya_type_id: Int!) { bank_account_name academy_org_id bank_branch + created_by + updated_by + current_job } } @@ -138,10 +138,7 @@ query getPersonById($id: Int!) { } } } - avinya_type { - id - name - } + avinya_type_id notes nic_no passport_no @@ -155,13 +152,17 @@ query getPersonById($id: Int!) { bank_account_name academy_org_id bank_branch + created_by + updated_by + current_job } } mutation updatePerson($person: Person!,$permanent_address: Address,$permanent_address_city: City, - $mailing_address: Address,$mailing_address_city: City) { - update_person(person: $person,permanent_address: $permanent_address,permanent_address_city: $permanent_address_city, - mailing_address: $mailing_address,mailing_address_city: $mailing_address_city) { + $mailing_address: Address,$mailing_address_city: City) { + update_person(person: $person,permanent_address: $permanent_address, + permanent_address_city: $permanent_address_city,mailing_address: $mailing_address, + mailing_address_city: $mailing_address_city) { id preferred_name full_name @@ -220,10 +221,7 @@ mutation updatePerson($person: Person!,$permanent_address: Address,$permanent_ad } } } - avinya_type { - id - name - } + avinya_type_id notes nic_no passport_no @@ -296,3 +294,84 @@ query getAllOrganizations { notes } } + +mutation insertPerson($person: Person!,$mailing_address: Address,$mailing_address_city: City) { + insert_person(person: $person,mailing_address: $mailing_address,mailing_address_city: $mailing_address_city) { + id + preferred_name + full_name + date_of_birth + sex + asgardeo_id + jwt_sub_id + created + updated + jwt_email + permanent_address { + city { + id + name { + name_en + name_si + name_ta + } + } + street_address + phone + id + } + mailing_address { + city { + id + name { + name_en + name_si + name_ta + } + } + street_address + phone + id + } + phone + organization { + id + description + notes + address { + id + } + avinya_type { + id + name + } + name { + name_en + } + parent_organizations { + id + name { + name_en + } + } + } + avinya_type_id + notes + nic_no + passport_no + id_no + email + street_address + digital_id + avinya_phone + bank_name + bank_account_number + bank_account_name + academy_org_id + bank_branch + created_by + updated_by + current_job + } +} + diff --git a/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/client.bal b/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/client.bal index e069424..103d701 100644 --- a/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/client.bal +++ b/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/client.bal @@ -28,19 +28,19 @@ public isolated client class GraphqlClient { self.graphqlClient = clientEp; } remote isolated function getPersons(int organization_id, int avinya_type_id) returns GetPersonsResponse|graphql:ClientError { - string query = string `query getPersons($organization_id:Int!,$avinya_type_id:Int!) {persons(organization_id:$organization_id,avinya_type_id:$avinya_type_id) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type {id name} notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch}}`; + string query = string `query getPersons($organization_id:Int!,$avinya_type_id:Int!) {persons(organization_id:$organization_id,avinya_type_id:$avinya_type_id) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type_id notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; map variables = {"organization_id": organization_id, "avinya_type_id": avinya_type_id}; json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetPersonsResponse); } remote isolated function getPersonById(int id) returns GetPersonByIdResponse|graphql:ClientError { - string query = string `query getPersonById($id:Int!) {person_by_id(id:$id) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type {id name} notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch}}`; + string query = string `query getPersonById($id:Int!) {person_by_id(id:$id) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type_id notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; map variables = {"id": id}; json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetPersonByIdResponse); } remote isolated function updatePerson(Person person, City? permanent_address_city = (), Address? mailing_address = (), Address? permanent_address = (), City? mailing_address_city = ()) returns UpdatePersonResponse|graphql:ClientError { - string query = string `mutation updatePerson($person:Person!,$permanent_address:Address,$permanent_address_city:City,$mailing_address:Address,$mailing_address_city:City) {update_person(person:$person,permanent_address:$permanent_address,permanent_address_city:$permanent_address_city,mailing_address:$mailing_address,mailing_address_city:$mailing_address_city) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type {id name} notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; + string query = string `mutation updatePerson($person:Person!,$permanent_address:Address,$permanent_address_city:City,$mailing_address:Address,$mailing_address_city:City) {update_person(person:$person,permanent_address:$permanent_address,permanent_address_city:$permanent_address_city,mailing_address:$mailing_address,mailing_address_city:$mailing_address_city) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type_id notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; map variables = {"permanent_address_city": permanent_address_city, "mailing_address": mailing_address, "person": person, "permanent_address": permanent_address, "mailing_address_city": mailing_address_city}; json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, UpdatePersonResponse); @@ -63,4 +63,10 @@ public isolated client class GraphqlClient { json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); return check performDataBinding(graphqlResponse, GetAllOrganizationsResponse); } + remote isolated function insertPerson(Person person, Address? mailing_address = (), City? mailing_address_city = ()) returns InsertPersonResponse|graphql:ClientError { + string query = string `mutation insertPerson($person:Person!,$mailing_address:Address,$mailing_address_city:City) {insert_person(person:$person,mailing_address:$mailing_address,mailing_address_city:$mailing_address_city) {id preferred_name full_name date_of_birth sex asgardeo_id jwt_sub_id created updated jwt_email permanent_address {city {id name {name_en name_si name_ta}} street_address phone id} mailing_address {city {id name {name_en name_si name_ta}} street_address phone id} phone organization {id description notes address {id} avinya_type {id name} name {name_en} parent_organizations {id name {name_en}}} avinya_type_id notes nic_no passport_no id_no email street_address digital_id avinya_phone bank_name bank_account_number bank_account_name academy_org_id bank_branch created_by updated_by current_job}}`; + map variables = {"mailing_address": mailing_address, "person": person, "mailing_address_city": mailing_address_city}; + json graphqlResponse = check self.graphqlClient->executeWithType(query, variables); + return check performDataBinding(graphqlResponse, InsertPersonResponse); + } } diff --git a/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/types.bal b/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/types.bal index eb52437..1467135 100644 --- a/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/types.bal +++ b/campus/bffs/enrollment/graphql_client/graphql_client_cg_src/types.bal @@ -488,10 +488,7 @@ public type GetPersonsResponse record {| |} name; |}[]? parent_organizations; |}? organization; - record {| - int? id; - string? name; - |}? avinya_type; + int? avinya_type_id; string? notes; string? nic_no; string? passport_no; @@ -505,6 +502,9 @@ public type GetPersonsResponse record {| string? bank_account_name; int? academy_org_id; string? bank_branch; + int? created_by; + int? updated_by; + string? current_job; |}[]? persons; |}; @@ -569,10 +569,7 @@ public type GetPersonByIdResponse record {| |} name; |}[]? parent_organizations; |}? organization; - record {| - int? id; - string? name; - |}? avinya_type; + int? avinya_type_id; string? notes; string? nic_no; string? passport_no; @@ -586,6 +583,9 @@ public type GetPersonByIdResponse record {| string? bank_account_name; int? academy_org_id; string? bank_branch; + int? created_by; + int? updated_by; + string? current_job; |}? person_by_id; |}; @@ -650,10 +650,7 @@ public type UpdatePersonResponse record {| |} name; |}[]? parent_organizations; |}? organization; - record {| - int? id; - string? name; - |}? avinya_type; + int? avinya_type_id; string? notes; string? nic_no; string? passport_no; @@ -728,3 +725,84 @@ public type GetAllOrganizationsResponse record {| string? notes; |}[]? all_organizations; |}; + +public type InsertPersonResponse record {| + map __extensions?; + record {| + int? id; + string? preferred_name; + string? full_name; + string? date_of_birth; + string? sex; + string? asgardeo_id; + string? jwt_sub_id; + string? created; + string? updated; + string? jwt_email; + record {| + record {| + int? id; + record {| + string? name_en; + string? name_si; + string? name_ta; + |} name; + |} city; + string? street_address; + int? phone; + int? id; + |}? permanent_address; + record {| + record {| + int? id; + record {| + string? name_en; + string? name_si; + string? name_ta; + |} name; + |} city; + string? street_address; + int? phone; + int? id; + |}? mailing_address; + int? phone; + record {| + int? id; + string? description; + string? notes; + record {| + int? id; + |}? address; + record {| + int? id; + string? name; + |}? avinya_type; + record {| + string? name_en; + |} name; + record {| + int? id; + record {| + string? name_en; + |} name; + |}[]? parent_organizations; + |}? organization; + int? avinya_type_id; + string? notes; + string? nic_no; + string? passport_no; + string? id_no; + string? email; + string? street_address; + string? digital_id; + int? avinya_phone; + string? bank_name; + string? bank_account_number; + string? bank_account_name; + int? academy_org_id; + string? bank_branch; + int? created_by; + int? updated_by; + string? current_job; + |}? insert_person; +|}; From da3da9a9e616d079d437eb8fe460829152a1a500 Mon Sep 17 00:00:00 2001 From: YujithIsura Date: Sat, 5 Oct 2024 11:44:31 +0530 Subject: [PATCH 4/5] person update WIP --- .../avinya/enrollment/lib/data/person.dart | 85 ++-- .../lib/widgets/student_update.dart | 382 +++++++++++++----- 2 files changed, 334 insertions(+), 133 deletions(-) diff --git a/campus/frontend/lib/avinya/enrollment/lib/data/person.dart b/campus/frontend/lib/avinya/enrollment/lib/data/person.dart index b953990..09c8893 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/data/person.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/data/person.dart @@ -245,44 +245,45 @@ class Person { int? academy_org_id; String? created; String? updated; + String? current_job; var parent_students = []; - Person({ - this.id, - this.record_type, - this.preferred_name, - this.full_name, - this.notes, - this.date_of_birth, - this.sex, - this.avinya_type_id, - this.passport_no, - this.permanent_address_id, - this.mailing_address_id, - this.nic_no, - this.id_no, - this.phone, - this.organization_id, - this.organization, - this.avinya_type, - this.asgardeo_id, - this.jwt_sub_id, - this.jwt_email, - this.email, - this.permanent_address, - this.mailing_address, - this.street_address, - this.bank_account_number, - this.bank_name, - this.bank_branch, - this.digital_id, - this.bank_account_name, - this.avinya_phone, - this.academy_org_id, - this.created, - this.updated, - this.parent_students = const [], - }); + Person( + {this.id, + this.record_type, + this.preferred_name, + this.full_name, + this.notes, + this.date_of_birth, + this.sex, + this.avinya_type_id, + this.passport_no, + this.permanent_address_id, + this.mailing_address_id, + this.nic_no, + this.id_no, + this.phone, + this.organization_id, + this.organization, + this.avinya_type, + this.asgardeo_id, + this.jwt_sub_id, + this.jwt_email, + this.email, + this.permanent_address, + this.mailing_address, + this.street_address, + this.bank_account_number, + this.bank_name, + this.bank_branch, + this.digital_id, + this.bank_account_name, + this.avinya_phone, + this.academy_org_id, + this.created, + this.updated, + this.parent_students = const [], + this.current_job}); factory Person.fromJson(Map json) { return Person( @@ -328,6 +329,7 @@ class Person { .map((eval_json) => Person.fromJson(eval_json)) .toList() : [], + current_job: json['current_job'], ); } @@ -348,7 +350,7 @@ class Person { if (nic_no != null) 'nic_no': nic_no, if (id_no != null) 'id_no': id_no, if (phone != null) 'phone': phone, - if (organization_id != null) 'organization_id': organization_id, + if (organization != null) 'organization_id': organization!.id, if (asgardeo_id != null) 'asgardeo_id': asgardeo_id, if (jwt_sub_id != null) 'jwt_sub_id': jwt_sub_id, if (jwt_email != null) 'jwt_email': jwt_email, @@ -361,16 +363,17 @@ class Person { if (bank_account_number != null) 'bank_account_number': bank_account_number, if (bank_name != null) 'bank_name': bank_name, - if (bank_branch != null) 'bank_name': bank_branch, + if (bank_branch != null) 'bank_branch': bank_branch, if (digital_id != null) 'digital_id': digital_id, if (bank_account_name != null) 'bank_account_name': bank_account_name, if (avinya_phone != null) 'avinya_phone': avinya_phone, if (academy_org_id != null) 'academy_org_id': academy_org_id, - if (organization != null) 'organization': organization!.toJson(), - if (avinya_type != null) 'avinya_type': avinya_type!.toJson(), + // if (organization != null) 'organization': organization!.toJson(), + // if (avinya_type != null) 'avinya_type': avinya_type!.toJson(), if (created != null) 'created': created, if (updated != null) 'updated': updated, - 'parent_students': [parent_students], + // 'parent_students': [parent_students], + if (current_job != null) 'current_job': current_job, }; map(DataRow Function(dynamic evaluation) param0) {} diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart index f778b13..cf24131 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart @@ -49,39 +49,88 @@ class _StudentUpdateState extends State { classes = classes; userPerson = user; selectedSex = userPerson.sex; - selectedCityId = userPerson.mailing_address?.city?.id; - selectedDistrictId = userPerson.mailing_address?.district?.id; - selectedOrgId = userPerson.organization?.id; - selectedClassId = userPerson.organization?.id; - selectedDateOfBirth = DateTime.tryParse(userPerson.date_of_birth ?? ''); + userPerson.avinya_type_id = user.avinya_type?.id; + + // Safely assign city and organization IDs with fallbacks + selectedCityId = userPerson.mailing_address?.city?.id ?? + 0; // Default to 0 or another fallback value + selectedOrgId = + userPerson.organization?.id ?? 0; // Similarly handle organization ID + selectedClassId = userPerson.organization?.id ?? + 0; // Ensure organization ID is used for class as well + + // Handling date of birth + String? dob = userPerson.date_of_birth; + print("Date of Birth String: $dob"); + + // Safely try parsing the date of birth + if (dob == null || dob.isEmpty) { + selectedDateOfBirth = DateTime.now(); // Default if dob is null or empty + } else { + try { + selectedDateOfBirth = + DateTime.parse(dob); // Use DateTime.parse directly + } catch (e) { + print('Error parsing date: $e'); + selectedDateOfBirth = + DateTime.now(); // Fallback to now if parsing fails + } + } }); } Future fetchDistrictList() async { List districtList = await fetchDistricts(); - setState(() { - districts = districtList; - }); + int? districtId = getDistrictIdByCityId(selectedCityId, districtList); + if (mounted) { + setState(() { + districts = districtList; + selectedDistrictId = districtId ?? selectedDistrictId; + // if (districtId != null) { + // // Update the cities based on the district + // selectedCityId = null; // Reset city selection when district changes + // } + }); + } } Future fetchOrganizationList() async { List orgList = await fetchOrganizations(); - setState(() { - organizations = orgList; - }); + if (mounted) { + setState(() { + organizations = orgList; + }); + } } Future fetchAvinyaTypeList() async { List avinyaTypeList = await fetchAvinyaTypes(); - setState(() { - avinyaTypes = avinyaTypeList; - }); + if (mounted) { + setState(() { + avinyaTypes = avinyaTypeList; + }); + } + } + + int? getDistrictIdByCityId(int? selectedCityId, List districtList) { + for (var district in districtList) { + if (district.cities != null) { + // Check if cities is not null + for (var city in district.cities!) { + // Use the non-null assertion operator + if (city.id == selectedCityId) { + return district.id; // Return the district ID if the city ID matches + } + } + } + } + return null; // Return null if no matching district is found } @override Widget build(BuildContext context) { return Scaffold( - body: userPerson.full_name == null + body: userPerson.preferred_name == null ? const Center(child: CircularProgressIndicator()) : SingleChildScrollView( padding: const EdgeInsets.all(16.0), @@ -108,6 +157,7 @@ class _StudentUpdateState extends State { _buildDateOfBirthField(context), _buildSexField(), const SizedBox(height: 10), + _buildOrganizationField(), _buildStudentClassField(), // Student Class based on organization.description const SizedBox(height: 20), _buildSectionTitle(context, 'Contact Information'), @@ -139,7 +189,6 @@ class _StudentUpdateState extends State { userPerson.digital_id = value; }), _buildAvinyaTypeField(), - _buildOrganizationField(), const SizedBox(height: 20), _buildSectionTitle(context, 'Bank Information'), _buildEditableField('Bank Name', userPerson.bank_name, @@ -160,6 +209,16 @@ class _StudentUpdateState extends State { (value) { userPerson.bank_account_number = value; }), + const SizedBox(height: 20), + _buildSectionTitle(context, 'Professional Information'), + _buildEditableField( + 'Current Job', userPerson.current_job, (value) { + userPerson.current_job = value; + }), + _buildEditableTextArea('Comments', userPerson.notes, + (value) { + userPerson.notes = value; + }), const SizedBox(height: 40), _buildSaveButton(), ], @@ -192,7 +251,12 @@ class _StudentUpdateState extends State { style: Theme.of(context).textTheme.headline6, ), Text( - userPerson.organization?.name?.name_en ?? 'N/A', + userPerson.organization?.parent_organizations != null && + userPerson.organization!.parent_organizations!.isNotEmpty + ? userPerson.organization?.parent_organizations!.first.name + ?.name_en ?? + 'N/A' + : 'N/A', style: Theme.of(context).textTheme.subtitle1, ), ], @@ -241,6 +305,54 @@ class _StudentUpdateState extends State { ); } + Widget _buildEditableTextArea( + String label, String? initialValue, Function(String) onSave, + {int minLines = 1, int maxLines = 5}) { + // Set maxLines to a desired value for textarea + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + label, + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: TextFormField( + initialValue: initialValue ?? '', + decoration: InputDecoration( + contentPadding: + const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), + border: OutlineInputBorder(), + ), + onSaved: (value) => onSave(value!), + minLines: minLines, // Minimum lines to display + maxLines: maxLines, // Maximum lines to display + maxLength: 1000, // Maximum character limit + buildCounter: (BuildContext context, + {required int currentLength, + required bool isFocused, + required int? maxLength}) { + return Text( + '$currentLength/${maxLength ?? 1000}', // Display character count + style: TextStyle( + color: currentLength > (maxLength ?? 1000) + ? Colors.red + : null, // Change color if limit exceeded + ), + ); + }, + ), + ), + ], + ), + ); + } + Widget _buildSexField() { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), @@ -311,6 +423,26 @@ class _StudentUpdateState extends State { } Widget _buildCityField() { + List cityList = districts + .firstWhere( + (district) => district.id == selectedDistrictId, + orElse: () => District( + id: 0, + name: Name(name_en: 'Unknown'), + cities: [], + ), + ) + .cities ?? + []; + + // Ensure selectedCityId is valid or set to null if not found in the current city's list + if (cityList.isNotEmpty && selectedCityId != null) { + bool cityExists = cityList.any((city) => city.id == selectedCityId); + if (!cityExists) { + selectedCityId = null; + } + } + return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( @@ -325,16 +457,20 @@ class _StudentUpdateState extends State { Expanded( flex: 6, child: DropdownButtonFormField( - value: selectedCityId != null && - _getCityOptions() - .any((item) => item.value == selectedCityId) - ? selectedCityId - : null, // Set value to null if the selectedCityId is not in the list - items: _getCityOptions(), // Pass the city options + value: selectedCityId, + items: cityList + .map((city) => DropdownMenuItem( + value: city.id, + child: Text(city.name?.name_en ?? 'Unknown City'), + )) + .toList(), onChanged: (value) { setState(() { selectedCityId = value; userPerson.mailing_address?.city_id = value; + + // Debugging: print selected city + print("Selected City ID: $selectedCityId"); }); }, decoration: const InputDecoration( @@ -361,33 +497,82 @@ class _StudentUpdateState extends State { ), ), Expanded( - flex: 6, - child: DropdownButtonFormField( - value: userPerson.organization - ?.id, // Set the initial value to match one in the list - items: organizations.map((org) { - return DropdownMenuItem( - value: org.id, - child: Text(org.name?.name_en ?? 'Unknown'), - ); - }).toList(), - onChanged: (int? newValue) { - setState(() { - userPerson.organization_id = - newValue; // Update the organization ID - }); - }, - decoration: InputDecoration( - labelText: 'Select Organization', - border: OutlineInputBorder(), - ), - )), + flex: 6, + child: DropdownButtonFormField( + value: + userPerson.organization?.parent_organizations?.first.id ?? 0, + items: organizations + .where((org) => + org.avinya_type?.id == 105 || + org.avinya_type?.id == 86) // Filter organizations + .map((org) { + return DropdownMenuItem( + value: org.id, + child: Text(org.name?.name_en ?? 'Unknown'), + ); + }).toList(), + onChanged: (int? newValue) async { + classes = await fetchClasses(newValue); + setState(() { + classes = classes; + userPerson.organization_id = + newValue; // Update the organization ID + }); + }, + decoration: InputDecoration( + labelText: 'Select Organization', + border: OutlineInputBorder(), + ), + ), + ), ], ), ); } Widget _buildAvinyaTypeField() { + // Function to calculate age from a String date of birth + int? calculateAge(String? birthDateString) { + if (birthDateString == null || birthDateString.isEmpty) return null; + + try { + final birthDate = + DateTime.parse(birthDateString); // Parse the date string + final currentDate = DateTime.now(); + int age = currentDate.year - birthDate.year; + if (currentDate.month < birthDate.month || + (currentDate.month == birthDate.month && + currentDate.day < birthDate.day)) { + age--; + } + return age; + } catch (e) { + // Handle invalid date format + print('Invalid date format: $birthDateString'); + return null; + } + } + + // Determine the Avinya Type ID + final int? avinyaTypeId = (userPerson.date_of_birth != null && + calculateAge(userPerson.date_of_birth) != null && + calculateAge(userPerson.date_of_birth)! < 19) + ? 103 // Auto-select Future Enrollees if under 18 + : userPerson.avinya_type?.id; + + // Filter avinyaTypes based on the selected IDs + final filteredAvinyaTypes = avinyaTypes.where((org) { + return org.id == 26 || // student applicant + org.id == 37 || // empower-student + org.id == 10 || // Vocational IT + org.id == 96 || // Vocational CS + org.id == 93 || // dropout-empower-student + org.id == 100 || // dropout-cs-student + org.id == 99 || // dropout-it-student + org.id == 103 || // Future Enrollees + org.id == 94; // dropout-student-applicant + }).toList(); + return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( @@ -400,26 +585,30 @@ class _StudentUpdateState extends State { ), ), Expanded( - flex: 6, - child: DropdownButtonFormField( - value: userPerson.avinya_type_id, - items: avinyaTypes.map((org) { - return DropdownMenuItem( - value: org.id, - child: Text(org.name ?? 'Unknown'), - ); - }).toList(), - onChanged: (int? newValue) { - setState(() { - userPerson.avinya_type_id = - newValue; // Update the organization ID - }); - }, - decoration: InputDecoration( - labelText: 'Select Avinya Type', - border: OutlineInputBorder(), - ), - )), + flex: 6, + child: DropdownButtonFormField( + // Ensure the value matches one of the filtered Avinya Types + value: filteredAvinyaTypes.any((org) => org.id == avinyaTypeId) + ? avinyaTypeId + : null, + items: filteredAvinyaTypes.map((org) { + return DropdownMenuItem( + value: org.id, + child: Text(org.name ?? 'Unknown'), + ); + }).toList(), + onChanged: (int? newValue) { + setState(() { + userPerson.avinya_type_id = + newValue; // Update the Avinya Type ID + }); + }, + decoration: const InputDecoration( + labelText: 'Select Avinya Type', + border: OutlineInputBorder(), + ), + ), + ), ], ), ); @@ -531,36 +720,45 @@ class _StudentUpdateState extends State { } Widget _buildStudentClassField() { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Row( - children: [ - Expanded( - flex: 4, - child: Text( - 'Class', - style: Theme.of(context).textTheme.bodyText1, + if (classes.isEmpty) { + return Container(); + } else { + // Ensure selectedClassId exists in the class list, otherwise set it to null + final isValidClass = classes.any((cls) => cls.id == selectedClassId); + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + 'Class', + style: Theme.of(context).textTheme.bodyText1, + ), ), - ), - Expanded( - flex: 6, - child: DropdownButtonFormField( - value: selectedClassId, - items: _getClassOptions(), - onChanged: (value) { - setState(() { - selectedClassId = value; - userPerson.organization?.id = value; - }); - }, - decoration: const InputDecoration( - labelText: 'Select Class', - border: OutlineInputBorder(), + Expanded( + flex: 6, + child: DropdownButtonFormField( + value: isValidClass + ? selectedClassId + : null, // Validate selectedClassId + items: _getClassOptions(), + onChanged: (value) { + setState(() { + selectedClassId = value; + userPerson.organization?.id = value; + }); + }, + decoration: const InputDecoration( + labelText: 'Select Class', + border: OutlineInputBorder(), + ), ), ), - ), - ], - ), - ); + ], + ), + ); + } } } From 3ab354f8004eb8f3941dea623704cc02d8101973 Mon Sep 17 00:00:00 2001 From: YujithIsura Date: Mon, 7 Oct 2024 14:59:17 +0530 Subject: [PATCH 5/5] person enrollment module completed --- .../avinya/enrollment/lib/data/person.dart | 53 +- .../lib/screens/student_create_screen.dart | 26 + .../lib/widgets/person_data_excel_report.dart | 11 +- .../lib/widgets/student_create.dart | 736 ++++++++++++++++++ .../lib/widgets/student_update.dart | 88 ++- .../enrollment/lib/widgets/students.dart | 81 +- .../frontend/lib/widgets/error_message.dart | 27 + .../frontend/lib/widgets/success_message.dart | 2 +- 8 files changed, 939 insertions(+), 85 deletions(-) create mode 100644 campus/frontend/lib/avinya/enrollment/lib/screens/student_create_screen.dart create mode 100644 campus/frontend/lib/avinya/enrollment/lib/widgets/student_create.dart create mode 100644 campus/frontend/lib/widgets/error_message.dart diff --git a/campus/frontend/lib/avinya/enrollment/lib/data/person.dart b/campus/frontend/lib/avinya/enrollment/lib/data/person.dart index 09c8893..d5e592e 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/data/person.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/data/person.dart @@ -1,6 +1,7 @@ import 'dart:developer'; - -//import 'package:ShoolManagementSystem/src/data/address.dart'; +import 'package:gallery/avinya/enrollment/lib/screens/students_screen.dart'; +import 'package:gallery/widgets/success_message.dart'; +import 'package:gallery/widgets/error_message.dart'; import 'package:flutter/material.dart'; import 'package:gallery/config/app_config.dart'; import 'package:http/http.dart' as http; @@ -206,7 +207,8 @@ class Address { if (city_id != null) 'city_id': city_id, if (district_id != null) 'district_id': district_id, if (record_type != null) 'record_type': record_type, - if (city != null) 'city': city, + // if (city != null) 'city': city, + if (city != null) 'city': city!.toJson(), if (district != null) 'district': district, }; } @@ -351,6 +353,7 @@ class Person { if (id_no != null) 'id_no': id_no, if (phone != null) 'phone': phone, if (organization != null) 'organization_id': organization!.id, + if (organization_id != null) 'organization_id': organization_id, if (asgardeo_id != null) 'asgardeo_id': asgardeo_id, if (jwt_sub_id != null) 'jwt_sub_id': jwt_sub_id, if (jwt_email != null) 'jwt_email': jwt_email, @@ -425,26 +428,6 @@ class Province { }; } -// Future> fetchPersons() async { -// final response = await http.get( -// Uri.parse(AppConfig.campusAssetsBffApiUrl + '/student_applicant'), -// headers: { -// 'Content-Type': 'application/json; charset=UTF-8', -// 'accept': 'application/json', -// 'Authorization': 'Bearer ' + AppConfig.campusBffApiKey, -// }, -// ); - -// if (response.statusCode == 200) { -// var resultsJson = json.decode(response.body).cast>(); -// List persons = -// await resultsJson.map((json) => Person.fromJson(json)).toList(); -// return persons; -// } else { -// throw Exception('Failed to load Person'); -// } -// } - Future> fetchPersons( int organization_id, int avinya_type_id) async { final response = await http.get( @@ -485,22 +468,30 @@ Future fetchPerson(int? person_id) async { } } -Future createPerson(Person person) async { +Future createPerson(BuildContext context, Person person) async { final response = await http.post( - Uri.parse(AppConfig.campusEnrollmentsBffApiUrl + '/student_applicant'), + Uri.parse(AppConfig.campusEnrollmentsBffApiUrl + '/add_person'), headers: { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': 'Bearer ' + AppConfig.campusBffApiKey, }, body: jsonEncode(person.toJson()), ); - if (response.statusCode == 200) { - // var resultsJson = json.decode(response.body).cast>(); + if (response.statusCode == 201) { Person person = Person.fromJson(json.decode(response.body)); + showSuccessToast("Student Profile Successfully Created!"); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StudentsScreen(), + ), + ); return person; } else { log(response.body + " Status code =" + response.statusCode.toString()); - throw Exception('Failed to create Person.'); + showErrorToast("Student Account Already Exists"); + return person; + // throw Exception('Failed to create Person.'); } } @@ -514,9 +505,13 @@ Future updatePerson(Person person) async { body: jsonEncode(person.toJson()), ); if (response.statusCode == 200) { + showSuccessToast("Student Profile Successfully Updated!"); return response; } else { - throw Exception('Failed to update Person.'); + showErrorToast( + response.body + " Status code =" + response.statusCode.toString()); + return response; + // throw Exception('Failed to update Person.'); } } diff --git a/campus/frontend/lib/avinya/enrollment/lib/screens/student_create_screen.dart b/campus/frontend/lib/avinya/enrollment/lib/screens/student_create_screen.dart new file mode 100644 index 0000000..2d97d8d --- /dev/null +++ b/campus/frontend/lib/avinya/enrollment/lib/screens/student_create_screen.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:gallery/avinya/enrollment/lib/widgets/student_create.dart'; + +class StudentCreateScreen extends StatefulWidget { + final int? id; + + const StudentCreateScreen({Key? key, this.id}) : super(key: key); + + @override + State createState() => _StudentCreateScreenState(); +} + +class _StudentCreateScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Student Profile", style: TextStyle(color: Colors.black)), + backgroundColor: Color.fromARGB(255, 120, 224, 158), + ), + body: Container( + child: StudentCreate(id: widget.id), + ), + ); + } +} diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/person_data_excel_report.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/person_data_excel_report.dart index 3fcfe6b..50810bd 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/widgets/person_data_excel_report.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/person_data_excel_report.dart @@ -2,6 +2,7 @@ import 'package:excel/excel.dart'; import 'package:flutter/material.dart'; import 'package:attendance/data/activity_attendance.dart'; import 'package:gallery/avinya/enrollment/lib/data/person.dart'; +import 'package:gallery/avinya/enrollment/lib/widgets/students.dart'; import 'package:intl/intl.dart'; class PersonDataExcelReport extends StatefulWidget { @@ -9,8 +10,7 @@ class PersonDataExcelReport extends StatefulWidget { final List columnNames; final Function() updateExcelState; final bool isFetching; - final String formattedStartDate; - final String formattedEndDate; + final AvinyaTypeId selectedAvinyaTypeId; const PersonDataExcelReport( {Key? key, @@ -18,8 +18,7 @@ class PersonDataExcelReport extends StatefulWidget { required this.columnNames, required this.updateExcelState, required this.isFetching, - required this.formattedStartDate, - required this.formattedEndDate}) + required this.selectedAvinyaTypeId}) : super(key: key); @override @@ -104,7 +103,7 @@ class _PersonDataExcelReportState extends State { sheet .cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 0)) .value = - "Avinya Foundation Student Enrollment Records for ${widget.formattedEndDate}"; + "Avinya Foundation Student Enrollment Records for ${widget.selectedAvinyaTypeId}"; sheet .cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 0)) .cellStyle = organizationHeaderStyle; @@ -150,7 +149,7 @@ class _PersonDataExcelReportState extends State { excel.save( fileName: - "Student Enrollment Records for ${widget.formattedEndDate}.xlsx"); + "Student Enrollment Records for ${widget.selectedAvinyaTypeId}.xlsx"); } } diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/student_create.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/student_create.dart new file mode 100644 index 0000000..c098e58 --- /dev/null +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/student_create.dart @@ -0,0 +1,736 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:gallery/avinya/enrollment/lib/data/person.dart'; + +class StudentCreate extends StatefulWidget { + final int? id; + const StudentCreate({Key? key, this.id}) : super(key: key); + + @override + State createState() => _StudentCreateState(); +} + +class _StudentCreateState extends State { + late Person userPerson = Person(); + List districts = []; + List organizations = []; + List avinyaTypes = []; + List classes = []; + final GlobalKey _formKey = GlobalKey(); + + String? selectedSex; + int? selectedCityId; + int? selectedDistrictId; + int? selectedOrgId; + int? selectedClassId; + DateTime? selectedDateOfBirth; + + @override + void initState() { + super.initState(); + loadData(); + } + + Future loadData() async { + await fetchDistrictList(); + await fetchOrganizationList(); + await fetchAvinyaTypeList(); + } + + Future fetchDistrictList() async { + List districtList = await fetchDistricts(); + int? districtId = getDistrictIdByCityId(selectedCityId, districtList); + if (mounted) { + setState(() { + districts = districtList; + selectedDistrictId = districtId ?? selectedDistrictId; + }); + } + } + + Future fetchOrganizationList() async { + List orgList = await fetchOrganizations(); + if (mounted) { + setState(() { + organizations = orgList; + }); + } + } + + Future fetchAvinyaTypeList() async { + List avinyaTypeList = await fetchAvinyaTypes(); + if (mounted) { + setState(() { + avinyaTypes = avinyaTypeList; + }); + } + } + + int? getDistrictIdByCityId(int? selectedCityId, List districtList) { + for (var district in districtList) { + if (district.cities != null) { + // Check if cities is not null + for (var city in district.cities!) { + // Use the non-null assertion operator + if (city.id == selectedCityId) { + return district.id; // Return the district ID if the city ID matches + } + } + } + } + return null; // Return null if no matching district is found + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Center( + child: SizedBox( + width: 800, + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // _buildProfileHeader(context), + const SizedBox(height: 20), + _buildSectionTitle(context, 'Student Information'), + _buildEditableField( + 'Preferred Name', userPerson.preferred_name, (value) { + userPerson.preferred_name = value; + }, + validator: (value) => + value!.isEmpty ? 'Preferred name is required' : null), + _buildEditableField('Full Name', userPerson.full_name, + (value) { + userPerson.full_name = value; + }, + validator: (value) => + value!.isEmpty ? 'Full name is required' : null), + _buildEditableField('NIC Number', userPerson.nic_no, (value) { + userPerson.nic_no = value; + }, validator: _validateNIC), + _buildDateOfBirthField(context), + _buildSexField(), + const SizedBox(height: 10), + _buildOrganizationField(), + _buildStudentClassField(), // Student Class based on organization.description + const SizedBox(height: 20), + _buildSectionTitle(context, 'Contact Information'), + _buildEditableField('Personal Email', userPerson.email, + (value) { + userPerson.email = value; + }), // Email format validation + + _buildEditableField( + 'Phone', userPerson.phone?.toString() ?? '', (value) { + userPerson.phone = int.tryParse(value); + }, validator: _validatePhone), + _buildEditableField('Street Address', + userPerson.mailing_address?.street_address ?? 'N/A', + (value) { + if (userPerson.mailing_address == null) { + userPerson.mailing_address = + Address(street_address: value); + } else { + userPerson.mailing_address!.street_address = value; + } + }), + _buildDistrictField(), + _buildCityField(), + const SizedBox(height: 20), + _buildSectionTitle(context, 'Digital Information'), + _buildEditableField('Digital ID', userPerson.digital_id, + (value) { + userPerson.digital_id = value; + }), + _buildAvinyaTypeField(), + const SizedBox(height: 20), + _buildSectionTitle(context, 'Bank Information'), + _buildEditableField('Bank Name', userPerson.bank_name, + (value) { + userPerson.bank_name = value; + }), + _buildEditableField('Bank Branch', userPerson.bank_branch, + (value) { + userPerson.bank_branch = value; + }), + _buildEditableField( + 'Bank Account Name', userPerson.bank_account_name, + (value) { + userPerson.bank_account_name = value; + }), + _buildEditableField( + 'Account Number', userPerson.bank_account_number, + (value) { + userPerson.bank_account_number = value; + }), + const SizedBox(height: 20), + _buildSectionTitle(context, 'Professional Information'), + _buildEditableField('Current Job', userPerson.current_job, + (value) { + userPerson.current_job = value; + }), + _buildEditableTextArea('Comments', userPerson.notes, (value) { + userPerson.notes = value; + }), + const SizedBox(height: 40), + _buildSaveButton(context), + ], + ), + ), + ), + ), + ), + ); + } + + String? _validateEmail(String? value) { + if (value == null || value.isEmpty) { + return 'Email is required'; + } + final emailRegex = RegExp(r'^[^@]+@[^@]+\.[^@]+'); + if (!emailRegex.hasMatch(value)) { + return 'Enter a valid email address'; + } + return null; + } + + String? _validatePhone(String? value) { + if (value == null || value.isEmpty) { + return 'Phone number is required'; + } + final phoneRegex = RegExp(r'^[0-9]+$'); + if (!phoneRegex.hasMatch(value) || value.length < 10) { + return 'Enter a valid phone number (at least 10 digits)'; + } + return null; + } + + String? _validateNIC(String? value) { + if (value == null || value.isEmpty) { + return 'NIC Number is required'; + } + final oldNICRegex = RegExp(r'^\d{9}[vVxX]$'); + final newNICRegex = RegExp(r'^\d{12}$'); + if (!oldNICRegex.hasMatch(value) && !newNICRegex.hasMatch(value)) { + return 'Enter a valid NIC number (old or new format)'; + } + return null; + } + + Widget _buildSectionTitle(BuildContext context, String title) { + return Text( + title, + style: Theme.of(context) + .textTheme + .subtitle1! + .copyWith(fontWeight: FontWeight.bold), + ); + } + + Widget _buildEditableField( + String label, String? initialValue, Function(String) onSave, + {String? Function(String?)? validator}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + label, + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: TextFormField( + initialValue: initialValue ?? '', + decoration: InputDecoration( + contentPadding: + const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), + border: OutlineInputBorder(), + ), + onSaved: (value) => onSave(value!), + validator: validator, // Adding validation here + ), + ), + ], + ), + ); + } + + Widget _buildEditableTextArea( + String label, String? initialValue, Function(String) onSave, + {int minLines = 1, int maxLines = 5}) { + // Set maxLines to a desired value for textarea + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + label, + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: TextFormField( + initialValue: initialValue ?? '', + decoration: InputDecoration( + contentPadding: + const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), + border: OutlineInputBorder(), + ), + onSaved: (value) => onSave(value!), + minLines: minLines, // Minimum lines to display + maxLines: maxLines, // Maximum lines to display + maxLength: 1000, // Maximum character limit + buildCounter: (BuildContext context, + {required int currentLength, + required bool isFocused, + required int? maxLength}) { + return Text( + '$currentLength/${maxLength ?? 1000}', // Display character count + style: TextStyle( + color: currentLength > (maxLength ?? 1000) + ? Colors.red + : null, // Change color if limit exceeded + ), + ); + }, + ), + ), + ], + ), + ); + } + + Widget _buildSexField() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + 'Sex', + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: DropdownButtonFormField( + value: selectedSex, + items: ['Male', 'Female', 'Other'] + .map((sex) => DropdownMenuItem(value: sex, child: Text(sex))) + .toList(), + onChanged: (value) { + setState(() { + selectedSex = value; + userPerson.sex = value; + }); + }, + decoration: const InputDecoration( + border: OutlineInputBorder(), + ), + ), + ), + ], + ), + ); + } + + Widget _buildDistrictField() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + 'District', + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: DropdownButtonFormField( + value: selectedDistrictId, + items: _getDistrictOptions(), + onChanged: (value) { + setState(() { + selectedDistrictId = value; + userPerson.mailing_address?.district_id = value; + }); + }, + decoration: const InputDecoration( + labelText: 'Select District', + border: OutlineInputBorder(), + ), + ), + ), + ], + ), + ); + } + + Widget _buildCityField() { + List cityList = districts + .firstWhere( + (district) => district.id == selectedDistrictId, + orElse: () => District( + id: 0, + name: Name(name_en: 'Unknown'), + cities: [], + ), + ) + .cities ?? + []; + + // Ensure selectedCityId is valid or set to null if not found in the current city's list + if (cityList.isNotEmpty && selectedCityId != null) { + bool cityExists = cityList.any((city) => city.id == selectedCityId); + if (!cityExists) { + selectedCityId = null; + } + } + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + 'City', + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: DropdownButtonFormField( + value: selectedCityId, + items: cityList + .map((city) => DropdownMenuItem( + value: city.id, + child: Text(city.name?.name_en ?? 'Unknown City'), + )) + .toList(), + onChanged: (value) { + setState(() { + selectedCityId = value; + + if (userPerson.mailing_address == null) { + // Create a new Address object with city_id set to value + userPerson.mailing_address = Address( + city_id: value, // Use named parameter for city_id + id: null); + userPerson.mailing_address!.city = City( + id: value, // Use named parameter for city_id + ); + } else { + userPerson.mailing_address!.city = City( + id: value, // Use named parameter for city_id + ); + } + + // Debugging: print selected city + print("Selected City ID: $selectedCityId"); + }); + }, + decoration: const InputDecoration( + labelText: 'Select City', + border: OutlineInputBorder(), + ), + ), + ), + ], + ), + ); + } + + Widget _buildOrganizationField() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + 'Main Organization', + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: DropdownButtonFormField( + value: userPerson.organization?.parent_organizations?.first.id, + items: [ + DropdownMenuItem( + value: null, // Default item for when no selection is made + child: const Text('Select an organization'), + ), + ...organizations + .where((org) => + org.avinya_type?.id == 105 || + org.avinya_type?.id == 86) // Filter organizations + .map((org) { + return DropdownMenuItem( + value: org.id, + child: Text(org.name?.name_en ?? 'Unknown'), + ); + }).toList(), + ], + onChanged: (int? newValue) async { + if (newValue != null) { + classes = await fetchClasses(newValue); + } + setState(() { + userPerson.organization_id = + newValue; // Update the organization ID + }); + }, + decoration: InputDecoration( + labelText: 'Select Organization', + border: OutlineInputBorder(), + ), + validator: (value) { + if (value == null) { + return 'Main organization is required'; + } + return null; // No error if a value is selected + }, + ), + ), + ], + ), + ); + } + + Widget _buildAvinyaTypeField() { + // Function to calculate age from a String date of birth + int? calculateAge(String? birthDateString) { + if (birthDateString == null || birthDateString.isEmpty) return null; + + try { + final birthDate = + DateTime.parse(birthDateString); // Parse the date string + final currentDate = DateTime.now(); + int age = currentDate.year - birthDate.year; + if (currentDate.month < birthDate.month || + (currentDate.month == birthDate.month && + currentDate.day < birthDate.day)) { + age--; + } + return age; + } catch (e) { + // Handle invalid date format + print('Invalid date format: $birthDateString'); + return null; + } + } + + // Determine the Avinya Type ID + final int? avinyaTypeId = (userPerson.date_of_birth != null && + calculateAge(userPerson.date_of_birth) != null && + calculateAge(userPerson.date_of_birth)! < 19) + ? 103 // Auto-select Future Enrollees if under 18 + : (userPerson.avinya_type_id ?? + userPerson.avinya_type + ?.id); // Fallback to avinya_type?.id if avinya_type_id is null + + // Filter avinyaTypes based on the selected IDs + final filteredAvinyaTypes = avinyaTypes.where((org) { + return org.id == 26 || // student applicant + org.id == 37 || // empower-student + org.id == 10 || // Vocational IT + org.id == 96 || // Vocational CS + org.id == 93 || // dropout-empower-student + org.id == 100 || // dropout-cs-student + org.id == 99 || // dropout-it-student + org.id == 103 || // Future Enrollees + org.id == 94; // dropout-student-applicant + }).toList(); + + filteredAvinyaTypes.any((org) => org.id == avinyaTypeId) + ? userPerson.avinya_type_id = avinyaTypeId + : null; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + 'Avinya Type', + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: DropdownButtonFormField( + // Ensure the value matches one of the filtered Avinya Types + value: filteredAvinyaTypes.any((org) => org.id == avinyaTypeId) + ? avinyaTypeId + : null, + items: filteredAvinyaTypes.map((org) { + return DropdownMenuItem( + value: org.id, + child: Text(org.name ?? 'Unknown'), + ); + }).toList(), + onChanged: (int? newValue) { + setState(() { + userPerson.avinya_type_id = + newValue; // Update the Avinya Type ID + }); + }, + decoration: const InputDecoration( + labelText: 'Select Avinya Type', + border: OutlineInputBorder(), + ), + validator: (value) { + if (value == null) { + return 'Avinya Type is required'; + } + return null; // No error if a value is selected + }, + ), + ), + ], + ), + ); + } + + List> _getDistrictOptions() { + return districts.map((district) { + return DropdownMenuItem( + value: district.id as int, + child: Text(district.name?.name_en ?? 'Unknown'), + ); + }).toList(); + } + + List> _getClassOptions() { + return classes + .map((classe) => DropdownMenuItem( + value: classe.id, // Access id directly from MainOrganization + child: Text(classe.description ?? + 'No description'), // Handle null description + )) + .toList(); + } + + Widget _buildSaveButton(context) { + return Center( + child: ElevatedButton( + onPressed: () { + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + // Save userPerson changes + createPerson(context, userPerson); + } + }, + child: const Text('Create Student'), + ), + ); + } + + Widget _buildDateOfBirthField(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + 'Date of Birth', + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: InkWell( + onTap: () async { + DateTime? pickedDate = await showDatePicker( + context: context, + initialDate: selectedDateOfBirth ?? DateTime(2000), + firstDate: DateTime(1900), + lastDate: DateTime.now(), + ); + if (pickedDate != null) { + setState(() { + selectedDateOfBirth = pickedDate; + userPerson.date_of_birth = + DateFormat('yyyy-MM-dd').format(pickedDate); + }); + } + }, + child: InputDecorator( + decoration: const InputDecoration( + labelText: 'Select Date Of Birth', + border: OutlineInputBorder(), + ), + child: Text( + selectedDateOfBirth == null + ? 'Select Date' + : DateFormat('d MMM, yyyy').format(selectedDateOfBirth!), + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildStudentClassField() { + if (classes.isEmpty) { + return Container(); + } else { + // Ensure selectedClassId exists in the class list, otherwise set it to null + final isValidClass = classes.any((cls) => cls.id == selectedClassId); + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text( + 'Class', + style: Theme.of(context).textTheme.bodyText1, + ), + ), + Expanded( + flex: 6, + child: DropdownButtonFormField( + value: isValidClass + ? selectedClassId + : null, // Validate selectedClassId + items: _getClassOptions(), + onChanged: (value) { + setState(() { + selectedClassId = value; + userPerson.organization?.id = value; + }); + }, + decoration: const InputDecoration( + labelText: 'Select Class', + border: OutlineInputBorder(), + ), + ), + ), + ], + ), + ); + } + } +} diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart index cf24131..1b10833 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/student_update.dart @@ -49,7 +49,7 @@ class _StudentUpdateState extends State { classes = classes; userPerson = user; selectedSex = userPerson.sex; - userPerson.avinya_type_id = user.avinya_type?.id; + userPerson.avinya_type_id = user.avinya_type_id; // Safely assign city and organization IDs with fallbacks selectedCityId = userPerson.mailing_address?.city?.id ?? @@ -149,11 +149,21 @@ class _StudentUpdateState extends State { 'Preferred Name', userPerson.preferred_name, (value) { userPerson.preferred_name = value; - }), + }, + validator: (value) => value!.isEmpty + ? 'Preferred name is required' + : null), _buildEditableField('Full Name', userPerson.full_name, (value) { userPerson.full_name = value; - }), + }, + validator: (value) => value!.isEmpty + ? 'Full name is required' + : null), + _buildEditableField('NIC Number', userPerson.nic_no, + (value) { + userPerson.nic_no = value; + }, validator: _validateNIC), _buildDateOfBirthField(context), _buildSexField(), const SizedBox(height: 10), @@ -169,7 +179,7 @@ class _StudentUpdateState extends State { 'Phone', userPerson.phone?.toString() ?? '', (value) { userPerson.phone = int.tryParse(value); - }), + }, validator: _validatePhone), _buildEditableField('Street Address', userPerson.mailing_address?.street_address ?? 'N/A', (value) { @@ -248,8 +258,9 @@ class _StudentUpdateState extends State { children: [ Text( userPerson.full_name ?? 'N/A', - style: Theme.of(context).textTheme.headline6, + style: Theme.of(context).textTheme.headlineMedium, ), + const SizedBox(width: 16), Text( userPerson.organization?.parent_organizations != null && userPerson.organization!.parent_organizations!.isNotEmpty @@ -257,7 +268,14 @@ class _StudentUpdateState extends State { ?.name_en ?? 'N/A' : 'N/A', - style: Theme.of(context).textTheme.subtitle1, + style: Theme.of(context).textTheme.bodyLarge, + ), + const SizedBox(width: 56), + Text( + userPerson.organization?.avinya_type != null + ? userPerson.organization?.avinya_type!.name ?? 'N/A' + : 'N/A', + style: Theme.of(context).textTheme.bodySmall, ), ], ), @@ -265,6 +283,29 @@ class _StudentUpdateState extends State { ); } + String? _validateNIC(String? value) { + if (value == null || value.isEmpty) { + return 'NIC Number is required'; + } + final oldNICRegex = RegExp(r'^\d{9}[vVxX]$'); + final newNICRegex = RegExp(r'^\d{12}$'); + if (!oldNICRegex.hasMatch(value) && !newNICRegex.hasMatch(value)) { + return 'Enter a valid NIC number (old or new format)'; + } + return null; + } + + String? _validatePhone(String? value) { + if (value == null || value.isEmpty) { + return 'Phone number is required'; + } + final phoneRegex = RegExp(r'^[0-9]+$'); + if (!phoneRegex.hasMatch(value) || value.length < 10) { + return 'Enter a valid phone number (at least 10 digits)'; + } + return null; + } + Widget _buildSectionTitle(BuildContext context, String title) { return Text( title, @@ -276,7 +317,8 @@ class _StudentUpdateState extends State { } Widget _buildEditableField( - String label, String? initialValue, Function(String) onSave) { + String label, String? initialValue, Function(String) onSave, + {String? Function(String?)? validator}) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( @@ -298,6 +340,7 @@ class _StudentUpdateState extends State { border: OutlineInputBorder(), ), onSaved: (value) => onSave(value!), + validator: validator, // Adding validation here ), ), ], @@ -467,7 +510,20 @@ class _StudentUpdateState extends State { onChanged: (value) { setState(() { selectedCityId = value; - userPerson.mailing_address?.city_id = value; + + if (userPerson.mailing_address == null) { + // Create a new Address object with city_id set to value + userPerson.mailing_address = Address( + city_id: value, // Use named parameter for city_id + id: null); + userPerson.mailing_address!.city = City( + id: value, // Use named parameter for city_id + ); + } else { + userPerson.mailing_address!.city = City( + id: value, // Use named parameter for city_id + ); + } // Debugging: print selected city print("Selected City ID: $selectedCityId"); @@ -523,6 +579,12 @@ class _StudentUpdateState extends State { labelText: 'Select Organization', border: OutlineInputBorder(), ), + validator: (value) { + if (value == null) { + return 'Main organization is required'; + } + return null; // No error if a value is selected + }, ), ), ], @@ -558,7 +620,9 @@ class _StudentUpdateState extends State { calculateAge(userPerson.date_of_birth) != null && calculateAge(userPerson.date_of_birth)! < 19) ? 103 // Auto-select Future Enrollees if under 18 - : userPerson.avinya_type?.id; + : (userPerson.avinya_type_id ?? + userPerson.avinya_type + ?.id); // Fallback to avinya_type?.id if avinya_type_id is null // Filter avinyaTypes based on the selected IDs final filteredAvinyaTypes = avinyaTypes.where((org) { @@ -607,6 +671,12 @@ class _StudentUpdateState extends State { labelText: 'Select Avinya Type', border: OutlineInputBorder(), ), + validator: (value) { + if (value == null) { + return 'Avinya Type is required'; + } + return null; // No error if a value is selected + }, ), ), ], diff --git a/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart b/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart index c3232f8..617bc3a 100644 --- a/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart +++ b/campus/frontend/lib/avinya/enrollment/lib/widgets/students.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:attendance/data/organization.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:gallery/avinya/enrollment/lib/data/person.dart'; +import 'package:gallery/avinya/enrollment/lib/screens/student_create_screen.dart'; import 'package:gallery/avinya/enrollment/lib/screens/student_update_screen.dart'; import 'person_data_excel_report.dart'; @@ -95,13 +96,11 @@ class _StudentsState extends State { void updateExcelState() { PersonDataExcelReport( - fetchedPersonData: _fetchedPersonData, - columnNames: columnNames, - updateExcelState: updateExcelState, - isFetching: _isFetching, - formattedStartDate: _selectedValue!.organization_metadata[0].value!, - formattedEndDate: _selectedValue!.organization_metadata[1].value!, - ); + fetchedPersonData: _fetchedPersonData, + columnNames: columnNames, + updateExcelState: updateExcelState, + isFetching: _isFetching, + selectedAvinyaTypeId: _selectedAvinyaTypeId); } @override @@ -141,26 +140,6 @@ class _StudentsState extends State { return ColumnNames; } - // void searchStudents(String query) { - // setState(() { - // if (query.isEmpty) { - // filteredStudents = _fetchedPersonData; - // } else { - // query = query.toLowerCase(); - - // filteredStudents = _fetchedPersonData.where((student) { - // final presentCountString = - // student.present_count?.toString().toLowerCase() ?? ''; - // final attendancePercentageString = - // student.present_attendance_percentage?.toString().toLowerCase() ?? - // ''; - - // return presentCountString.contains(query) || - // attendancePercentageString.contains(query); - // }).toList(); - // } - // }); - // } void searchStudents(String query) { setState(() { if (query.isEmpty) { @@ -391,22 +370,44 @@ class _StudentsState extends State { Expanded( child: Container( alignment: Alignment.bottomRight, - margin: EdgeInsets.only(right: 20.0), + margin: const EdgeInsets.only(right: 20.0), width: 25.0, height: 30.0, - child: _selectedValue != null - ? PersonDataExcelReport( - fetchedPersonData: filteredStudents, - columnNames: columnNames, - updateExcelState: updateExcelState, - isFetching: _isFetching, - formattedStartDate: _selectedValue! - .organization_metadata[0].value!, - formattedEndDate: _selectedValue! - .organization_metadata[1].value!, - ) - : SizedBox(), + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StudentCreateScreen( + id: null, // Since it's for creating a new student, no ID is passed + ), + ), + ); + }, + child: const Text('Create New'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 10.0, vertical: 5.0), + textStyle: const TextStyle(fontSize: 16), + ), + ), ), + ), + SizedBox( + width: 10, + ), + Expanded( + child: Container( + alignment: Alignment.bottomRight, + margin: EdgeInsets.only(right: 20.0), + width: 25.0, + height: 30.0, + child: PersonDataExcelReport( + fetchedPersonData: filteredStudents, + columnNames: columnNames, + updateExcelState: updateExcelState, + isFetching: _isFetching, + selectedAvinyaTypeId: _selectedAvinyaTypeId)), ) ], ), diff --git a/campus/frontend/lib/widgets/error_message.dart b/campus/frontend/lib/widgets/error_message.dart new file mode 100644 index 0000000..28e95d0 --- /dev/null +++ b/campus/frontend/lib/widgets/error_message.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:oktoast/oktoast.dart'; + +void showErrorToast(item) { + showToastWidget( + Container( + padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(25.0), + color: Colors.red[600], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.check, color: Colors.white), + SizedBox(width: 12.0), + Text( + item.toString(), + style: TextStyle(color: Colors.white), + ), + ], + ), + ), + duration: Duration(seconds: 3), + position: ToastPosition.top, + ); +} diff --git a/campus/frontend/lib/widgets/success_message.dart b/campus/frontend/lib/widgets/success_message.dart index 2a50715..a6dc258 100644 --- a/campus/frontend/lib/widgets/success_message.dart +++ b/campus/frontend/lib/widgets/success_message.dart @@ -22,6 +22,6 @@ void showSuccessToast(item) { ), ), duration: Duration(seconds: 3), - position: ToastPosition.bottom, + position: ToastPosition.top, ); }