From 4ce95d9fbe38c91e6ee1373f7fb4ffaa7dce66f2 Mon Sep 17 00:00:00 2001 From: Nishant Samel Date: Wed, 26 Jun 2024 09:54:51 +0530 Subject: [PATCH 1/3] Remove redundant API for Teams and Profile page (#1854) * Remove duplicate routes * Remove `profile#show` action - Add user `id` to Team Member API response * Fix typographical error * Use the teams API instead of profile API * Remove duplicate routes * Remove `removeAvatar` and `updateAvatar` API from profile - Fix issue where team member avatar is not persisting - Set headers with `multipart/form-data` content type - Remove duplicate success message for `Image deleted successfully` * Update policies and respective specs * Common componet created for profile * fixed discussed bugs * Rename folder name from `context` to `Context` --------- Co-authored-by: Nishant Samel Co-authored-by: Shruti Apte --- .../internal_api/v1/profile_controller.rb | 11 - app/javascript/src/apis/profile.ts | 20 - app/javascript/src/apis/team.ts | 4 +- .../BankAccountDetails/AddressDetails.tsx | 125 ----- .../BankAccountDetails/BankDetails.tsx | 170 ------- .../Profile/BankAccountDetails/BankInfo.tsx | 56 --- .../BankAccountDetails/BillingDetailInput.tsx | 50 -- .../BankAccountDetails/CurrencyDropdown.tsx | 74 --- .../Profile/BankAccountDetails/index.tsx | 194 -------- .../Profile/Billing/Table/TableHeader.tsx | 39 -- .../Profile/Billing/Table/TableRow.tsx | 39 -- .../Profile/Billing/Table/index.tsx | 21 - .../src/components/Profile/Billing/index.tsx | 59 --- .../Profile/Common/DetailsHeader.tsx | 53 ++ .../components/Profile/Common/EditHeader.tsx | 78 +++ .../Profile/CommonComponents/Header/index.tsx | 7 - .../CompensationDetailsState.tsx | 0 .../EmploymentDetailsState.tsx | 0 .../PersonalDetailsState.tsx | 1 + .../src/components/Profile/DetailsHeader.tsx | 53 -- .../Profile/GoogleCalendar/Header.tsx | 20 - .../Profile/GoogleCalendar/index.tsx | 135 ------ .../src/components/Profile/Header.tsx | 83 ---- .../src/components/Profile/Layout.tsx | 68 --- .../src/components/Profile/Layout/Header.tsx | 41 ++ .../components/Profile/Layout/MobileNav.tsx | 160 ------ .../src/components/Profile/Layout/NavItem.tsx | 27 -- .../Profile/Layout/Navigation/AdminNav.tsx | 62 +++ .../Profile/Layout/Navigation/List.tsx | 34 ++ .../Profile/Layout/Navigation/MobileNav.tsx | 92 ++++ .../Navigation/UserInformation.tsx} | 66 ++- .../Profile/Layout/Navigation/index.tsx | 33 ++ .../Layout/OutletWrapper.tsx | 0 .../components/Profile/Layout/RouteConfig.tsx | 86 ++++ .../src/components/Profile/Layout/TeamUrl.tsx | 47 -- .../components/Profile/Layout/UserDetails.tsx | 173 ------- .../src/components/Profile/Layout/routes.tsx | 171 +++++++ .../Profile/Organization/Billing/index.tsx | 4 +- .../Profile/Organization/Details/index.tsx | 2 +- .../Profile/Organization/Edit/index.tsx | 4 +- .../Organization/Holidays/Details/Header.tsx | 31 -- .../Organization/Holidays/Details/index.tsx | 9 +- .../Holidays/EditHolidays/Header.tsx | 48 -- .../Holidays/EditHolidays/index.tsx | 8 +- .../Profile/Organization/Import/index.tsx | 4 +- .../Leaves/Details/CustomTableHeader.tsx | 28 -- .../Leaves/Details/CustomTableRow.tsx | 39 -- .../Organization/Leaves/Details/Header.tsx | 39 -- .../Organization/Leaves/Details/index.tsx | 29 +- .../Organization/Leaves/EditLeaves/Header.tsx | 52 -- .../Organization/Leaves/EditLeaves/index.tsx | 164 ++----- .../Organization/Leaves/EditLeaves/utils.js | 8 - .../Profile/Organization/Leaves/index.tsx | 139 +----- .../Profile/Organization/Leaves/utils.ts | 20 - .../Organization/Payment/StaticPage.tsx | 4 +- .../Compensation}/Edit/EditPage.tsx | 0 .../Compensation}/Edit/MobileEditPage.tsx | 0 .../Compensation}/Edit/index.tsx | 15 +- .../Compensation}/StaticPage.tsx | 0 .../Compensation}/index.tsx | 25 +- .../Devices}/Device.ts | 0 .../Devices}/Edit/EditPage.tsx | 0 .../Devices}/Edit/MobileEditPage.tsx | 0 .../Devices}/Edit/index.tsx | 42 +- .../Devices}/StaticPage.tsx | 0 .../Devices}/helpers.ts | 0 .../Devices}/index.tsx | 16 +- .../Employment}/Edit/MobileEditPage.tsx | 0 .../Employment}/Edit/StaticPage.tsx | 0 .../Employment}/Edit/index.tsx | 66 ++- .../Employment}/StaticPage.tsx | 0 .../Employment}/helpers.ts | 0 .../Employment}/index.tsx | 30 +- .../User}/Edit/MobileEditPage.tsx | 0 .../User}/Edit/StaticPage.tsx | 0 .../User}/Edit/index.tsx | 159 +++--- .../User}/Edit/validationSchema.ts | 0 .../User}/MobilePersonalDetails.tsx | 29 +- .../User}/StaticPage.tsx | 42 +- .../User}/index.tsx | 48 +- .../src/components/Profile/RouteConfig.tsx | 67 --- .../Schema/employmentSchema.ts} | 0 .../src/components/Profile/SubNav.tsx | 152 ------ .../components/Profile/UserDetail/index.tsx | 456 ------------------ .../src/components/Profile/constants.js | 76 --- .../Profile/context/EntryContext.tsx | 20 - .../src/components/Profile/index.tsx | 98 ++++ .../src/components/Profile/routes.ts | 106 ---- .../CompensationDetailsState.tsx | 7 - .../CompensationDetails/Edit/EditPage.tsx | 170 ------- .../Edit/MobileEditPage.tsx | 192 -------- .../CompensationDetails/Edit/index.tsx | 153 ------ .../CompensationDetails/StaticPage.tsx | 100 ---- .../Details/CompensationDetails/index.tsx | 71 --- .../Team/Details/DeviceDetails/StaticPage.tsx | 59 --- .../Team/Details/DeviceDetails/index.tsx | 14 - .../Team/Details/DocumentDetails/index.tsx | 12 - .../EmploymentDetails/Edit/StaticPage.tsx | 314 ------------ .../Details/EmploymentDetails/Edit/index.tsx | 330 ------------- .../EmploymentDetailsState.tsx | 17 - .../Details/EmploymentDetails/StaticPage.tsx | 111 ----- .../Team/Details/EmploymentDetails/index.tsx | 58 --- .../components/Team/Details/Layout/Header.tsx | 32 -- .../Team/Details/Layout/MobileNav.tsx | 47 -- .../Team/Details/Layout/SideNav.tsx | 192 -------- .../Team/Details/Layout/TeamUrl.tsx | 38 -- .../Team/Details/Layout/UserInformation.tsx | 150 ------ .../PersonalDetails/Edit/MobileEditPage.tsx | 427 ---------------- .../PersonalDetails/Edit/StaticPage.tsx | 417 ---------------- .../Details/PersonalDetails/Edit/index.tsx | 303 ------------ .../PersonalDetails/Edit/validationSchema.ts | 24 - .../PersonalDetails/MobilePersonalDetails.tsx | 101 ---- .../PersonalDetails/PersonalDetailsState.tsx | 20 - .../Details/PersonalDetails/StaticPage.tsx | 123 ----- .../Team/Details/PersonalDetails/index.tsx | 79 --- .../ReimburstmentDetails/StaticPage.tsx | 55 --- .../Details/ReimburstmentDetails/index.tsx | 14 - .../src/components/Team/Details/index.tsx | 52 -- .../components/Team/List/Table/TableRow.tsx | 2 +- .../src/components/Team/RouteConfig.tsx | 29 -- app/javascript/src/constants/routes.ts | 7 +- .../src/context/Profile/ProfileContext.tsx | 23 + .../src/context/TeamDetailsContext.tsx | 21 - app/javascript/src/mapper/teams.mapper.ts | 1 + app/policies/profile_policy.rb | 8 - app/policies/team_members/avatar_policy.rb | 2 +- app/policies/team_members/detail_policy.rb | 2 +- .../details/_detail.json.jbuilder | 2 +- config/routes/internal_api.rb | 8 +- .../v1/profile/remove_avatar_spec.rb | 21 - .../internal_api/v1/profile/show_spec.rb | 23 - .../v1/team_members/avatar/destroy_spec.rb | 34 +- .../v1/team_members/avatar/update_spec.rb | 2 +- .../v1/team_members/details/show_spec.rb | 18 +- .../v1/team_members/details/update_spec.rb | 13 +- 135 files changed, 1209 insertions(+), 7120 deletions(-) delete mode 100644 app/javascript/src/components/Profile/BankAccountDetails/AddressDetails.tsx delete mode 100644 app/javascript/src/components/Profile/BankAccountDetails/BankDetails.tsx delete mode 100644 app/javascript/src/components/Profile/BankAccountDetails/BankInfo.tsx delete mode 100644 app/javascript/src/components/Profile/BankAccountDetails/BillingDetailInput.tsx delete mode 100644 app/javascript/src/components/Profile/BankAccountDetails/CurrencyDropdown.tsx delete mode 100644 app/javascript/src/components/Profile/BankAccountDetails/index.tsx delete mode 100644 app/javascript/src/components/Profile/Billing/Table/TableHeader.tsx delete mode 100644 app/javascript/src/components/Profile/Billing/Table/TableRow.tsx delete mode 100644 app/javascript/src/components/Profile/Billing/Table/index.tsx delete mode 100644 app/javascript/src/components/Profile/Billing/index.tsx create mode 100644 app/javascript/src/components/Profile/Common/DetailsHeader.tsx create mode 100644 app/javascript/src/components/Profile/Common/EditHeader.tsx delete mode 100644 app/javascript/src/components/Profile/CommonComponents/Header/index.tsx rename app/javascript/src/components/Profile/{context => Context}/CompensationDetailsState.tsx (100%) rename app/javascript/src/components/Profile/{context => Context}/EmploymentDetailsState.tsx (100%) rename app/javascript/src/components/Profile/{context => Context}/PersonalDetailsState.tsx (97%) delete mode 100644 app/javascript/src/components/Profile/DetailsHeader.tsx delete mode 100644 app/javascript/src/components/Profile/GoogleCalendar/Header.tsx delete mode 100644 app/javascript/src/components/Profile/GoogleCalendar/index.tsx delete mode 100644 app/javascript/src/components/Profile/Header.tsx delete mode 100644 app/javascript/src/components/Profile/Layout.tsx create mode 100644 app/javascript/src/components/Profile/Layout/Header.tsx delete mode 100644 app/javascript/src/components/Profile/Layout/MobileNav.tsx delete mode 100644 app/javascript/src/components/Profile/Layout/NavItem.tsx create mode 100644 app/javascript/src/components/Profile/Layout/Navigation/AdminNav.tsx create mode 100644 app/javascript/src/components/Profile/Layout/Navigation/List.tsx create mode 100644 app/javascript/src/components/Profile/Layout/Navigation/MobileNav.tsx rename app/javascript/src/components/Profile/{CommonComponents/UserInformation/index.tsx => Layout/Navigation/UserInformation.tsx} (68%) create mode 100644 app/javascript/src/components/Profile/Layout/Navigation/index.tsx rename app/javascript/src/components/{Team/Details => Profile}/Layout/OutletWrapper.tsx (100%) create mode 100644 app/javascript/src/components/Profile/Layout/RouteConfig.tsx delete mode 100644 app/javascript/src/components/Profile/Layout/TeamUrl.tsx delete mode 100644 app/javascript/src/components/Profile/Layout/UserDetails.tsx create mode 100644 app/javascript/src/components/Profile/Layout/routes.tsx delete mode 100644 app/javascript/src/components/Profile/Organization/Holidays/Details/Header.tsx delete mode 100644 app/javascript/src/components/Profile/Organization/Holidays/EditHolidays/Header.tsx delete mode 100644 app/javascript/src/components/Profile/Organization/Leaves/Details/CustomTableHeader.tsx delete mode 100644 app/javascript/src/components/Profile/Organization/Leaves/Details/CustomTableRow.tsx delete mode 100644 app/javascript/src/components/Profile/Organization/Leaves/Details/Header.tsx delete mode 100644 app/javascript/src/components/Profile/Organization/Leaves/EditLeaves/Header.tsx rename app/javascript/src/components/Profile/{UserDetail/CompensationDetails => Personal/Compensation}/Edit/EditPage.tsx (100%) rename app/javascript/src/components/Profile/{UserDetail/CompensationDetails => Personal/Compensation}/Edit/MobileEditPage.tsx (100%) rename app/javascript/src/components/Profile/{UserDetail/CompensationDetails => Personal/Compensation}/Edit/index.tsx (90%) rename app/javascript/src/components/Profile/{UserDetail/CompensationDetails => Personal/Compensation}/StaticPage.tsx (100%) rename app/javascript/src/components/Profile/{UserDetail/CompensationDetails => Personal/Compensation}/index.tsx (71%) rename app/javascript/src/components/Profile/{UserDetail/AllocatedDevicesDetails => Personal/Devices}/Device.ts (100%) rename app/javascript/src/components/Profile/{UserDetail/AllocatedDevicesDetails => Personal/Devices}/Edit/EditPage.tsx (100%) rename app/javascript/src/components/Profile/{UserDetail/AllocatedDevicesDetails => Personal/Devices}/Edit/MobileEditPage.tsx (100%) rename app/javascript/src/components/Profile/{UserDetail/AllocatedDevicesDetails => Personal/Devices}/Edit/index.tsx (70%) rename app/javascript/src/components/Profile/{UserDetail/AllocatedDevicesDetails => Personal/Devices}/StaticPage.tsx (100%) rename app/javascript/src/components/Profile/{UserDetail/AllocatedDevicesDetails => Personal/Devices}/helpers.ts (100%) rename app/javascript/src/components/Profile/{UserDetail/AllocatedDevicesDetails => Personal/Devices}/index.tsx (72%) rename app/javascript/src/components/Profile/{UserDetail/EmploymentDetails => Personal/Employment}/Edit/MobileEditPage.tsx (100%) rename app/javascript/src/components/Profile/{UserDetail/EmploymentDetails => Personal/Employment}/Edit/StaticPage.tsx (100%) rename app/javascript/src/components/Profile/{UserDetail/EmploymentDetails => Personal/Employment}/Edit/index.tsx (86%) rename app/javascript/src/components/Profile/{UserDetail/EmploymentDetails => Personal/Employment}/StaticPage.tsx (100%) rename app/javascript/src/components/Profile/{UserDetail/EmploymentDetails => Personal/Employment}/helpers.ts (100%) rename app/javascript/src/components/Profile/{UserDetail/EmploymentDetails => Personal/Employment}/index.tsx (61%) rename app/javascript/src/components/Profile/{UserDetail => Personal/User}/Edit/MobileEditPage.tsx (100%) rename app/javascript/src/components/Profile/{UserDetail => Personal/User}/Edit/StaticPage.tsx (100%) rename app/javascript/src/components/Profile/{UserDetail => Personal/User}/Edit/index.tsx (71%) rename app/javascript/src/components/Profile/{UserDetail => Personal/User}/Edit/validationSchema.ts (100%) rename app/javascript/src/components/Profile/{UserDetail/UserDetailsView => Personal/User}/MobilePersonalDetails.tsx (85%) rename app/javascript/src/components/Profile/{UserDetail/UserDetailsView => Personal/User}/StaticPage.tsx (83%) rename app/javascript/src/components/Profile/{UserDetail/UserDetailsView => Personal/User}/index.tsx (64%) delete mode 100644 app/javascript/src/components/Profile/RouteConfig.tsx rename app/javascript/src/components/{Team/Details/EmploymentDetails/Edit/validationSchema.ts => Profile/Schema/employmentSchema.ts} (100%) delete mode 100644 app/javascript/src/components/Profile/SubNav.tsx delete mode 100644 app/javascript/src/components/Profile/UserDetail/index.tsx delete mode 100644 app/javascript/src/components/Profile/constants.js delete mode 100644 app/javascript/src/components/Profile/context/EntryContext.tsx create mode 100644 app/javascript/src/components/Profile/index.tsx delete mode 100644 app/javascript/src/components/Profile/routes.ts delete mode 100644 app/javascript/src/components/Team/Details/CompensationDetails/CompensationDetailsState.tsx delete mode 100644 app/javascript/src/components/Team/Details/CompensationDetails/Edit/EditPage.tsx delete mode 100644 app/javascript/src/components/Team/Details/CompensationDetails/Edit/MobileEditPage.tsx delete mode 100644 app/javascript/src/components/Team/Details/CompensationDetails/Edit/index.tsx delete mode 100644 app/javascript/src/components/Team/Details/CompensationDetails/StaticPage.tsx delete mode 100644 app/javascript/src/components/Team/Details/CompensationDetails/index.tsx delete mode 100644 app/javascript/src/components/Team/Details/DeviceDetails/StaticPage.tsx delete mode 100644 app/javascript/src/components/Team/Details/DeviceDetails/index.tsx delete mode 100644 app/javascript/src/components/Team/Details/DocumentDetails/index.tsx delete mode 100644 app/javascript/src/components/Team/Details/EmploymentDetails/Edit/StaticPage.tsx delete mode 100644 app/javascript/src/components/Team/Details/EmploymentDetails/Edit/index.tsx delete mode 100644 app/javascript/src/components/Team/Details/EmploymentDetails/EmploymentDetailsState.tsx delete mode 100644 app/javascript/src/components/Team/Details/EmploymentDetails/StaticPage.tsx delete mode 100644 app/javascript/src/components/Team/Details/EmploymentDetails/index.tsx delete mode 100644 app/javascript/src/components/Team/Details/Layout/Header.tsx delete mode 100644 app/javascript/src/components/Team/Details/Layout/MobileNav.tsx delete mode 100644 app/javascript/src/components/Team/Details/Layout/SideNav.tsx delete mode 100644 app/javascript/src/components/Team/Details/Layout/TeamUrl.tsx delete mode 100644 app/javascript/src/components/Team/Details/Layout/UserInformation.tsx delete mode 100644 app/javascript/src/components/Team/Details/PersonalDetails/Edit/MobileEditPage.tsx delete mode 100644 app/javascript/src/components/Team/Details/PersonalDetails/Edit/StaticPage.tsx delete mode 100644 app/javascript/src/components/Team/Details/PersonalDetails/Edit/index.tsx delete mode 100644 app/javascript/src/components/Team/Details/PersonalDetails/Edit/validationSchema.ts delete mode 100644 app/javascript/src/components/Team/Details/PersonalDetails/MobilePersonalDetails.tsx delete mode 100644 app/javascript/src/components/Team/Details/PersonalDetails/PersonalDetailsState.tsx delete mode 100644 app/javascript/src/components/Team/Details/PersonalDetails/StaticPage.tsx delete mode 100644 app/javascript/src/components/Team/Details/PersonalDetails/index.tsx delete mode 100644 app/javascript/src/components/Team/Details/ReimburstmentDetails/StaticPage.tsx delete mode 100644 app/javascript/src/components/Team/Details/ReimburstmentDetails/index.tsx delete mode 100644 app/javascript/src/components/Team/Details/index.tsx delete mode 100644 app/javascript/src/components/Team/RouteConfig.tsx create mode 100644 app/javascript/src/context/Profile/ProfileContext.tsx delete mode 100644 app/javascript/src/context/TeamDetailsContext.tsx delete mode 100644 spec/requests/internal_api/v1/profile/remove_avatar_spec.rb delete mode 100644 spec/requests/internal_api/v1/profile/show_spec.rb diff --git a/app/controllers/internal_api/v1/profile_controller.rb b/app/controllers/internal_api/v1/profile_controller.rb index 2fb1acec20..4e1b1cf8a3 100644 --- a/app/controllers/internal_api/v1/profile_controller.rb +++ b/app/controllers/internal_api/v1/profile_controller.rb @@ -1,17 +1,6 @@ # frozen_string_literal: true class InternalApi::V1::ProfileController < InternalApi::V1::ApplicationController - def show - authorize :show, policy_class: ProfilePolicy - render :show, locals: { user: current_user }, status: :ok - end - - def remove_avatar - authorize :remove_avatar, policy_class: ProfilePolicy - current_user.avatar.destroy - render json: { notice: "Avatar deleted successfully" }, status: :ok - end - def update authorize :update, policy_class: ProfilePolicy service = UpdateProfileSettingsService.new(current_user, user_params).process diff --git a/app/javascript/src/apis/profile.ts b/app/javascript/src/apis/profile.ts index 9cddc740ec..9686dd9c9a 100644 --- a/app/javascript/src/apis/profile.ts +++ b/app/javascript/src/apis/profile.ts @@ -2,30 +2,10 @@ import axios from "./api"; const path = "/profile"; -const index = () => axios.get(path); - -const getAddress = id => axios.get(`/users/${id}/addresses`); - const update = payload => axios.put(`${path}`, payload); -const upadteAvatar = (payload, config) => axios.put(`${path}`, payload, config); - -const createAddress = (userId, payload) => - axios.post(`/users/${userId}/addresses`, payload); - -const updateAddress = (userId, addressId, payload) => - axios.put(`/users/${userId}/addresses/${addressId}`, payload); - -const removeAvatar = () => axios.delete(`${path}/remove_avatar`); - const profileApi = { - index, update, - removeAvatar, - getAddress, - updateAddress, - createAddress, - upadteAvatar, }; export default profileApi; diff --git a/app/javascript/src/apis/team.ts b/app/javascript/src/apis/team.ts index 4e47068feb..d8d369b219 100644 --- a/app/javascript/src/apis/team.ts +++ b/app/javascript/src/apis/team.ts @@ -16,8 +16,8 @@ const updateTeamMember = (id, payload) => axios.put(`${path}/${id}`, payload); const destroyTeamMemberAvatar = id => axios.delete(`${path}/${id}/avatar`); -const updateTeamMemberAvatar = (id, payload) => - axios.put(`${path}/${id}/avatar`, payload); +const updateTeamMemberAvatar = (id, payload, config) => + axios.put(`${path}/${id}/avatar`, payload, config); const updateTeamMembers = payload => axios.put(`${path}/update_team_members`, payload); diff --git a/app/javascript/src/components/Profile/BankAccountDetails/AddressDetails.tsx b/app/javascript/src/components/Profile/BankAccountDetails/AddressDetails.tsx deleted file mode 100644 index bedb0b7538..0000000000 --- a/app/javascript/src/components/Profile/BankAccountDetails/AddressDetails.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import React from "react"; - -const AddressDetails = ({ - fields, - handleAddressDetails, - handleBankDetails, - recipientDetails, -}) => { - const legalType = fields.filter( - field => field["group"][0]["key"] == "legalType" - )[0]; - - const countries = fields.filter( - field => field["group"][0]["key"] == "address.country" - )[0]; - - const postCode = fields.filter( - field => field["group"][0]["key"] == "address.postCode" - )[0]; - - return ( -
-
-
- -
- -
-
-
- -
- -
-
-
-
-
- -
- - handleAddressDetails(name, value, { - validationRegexp: "^.{1,255}$", - }) - } - /> -
-
-
- -
- - handleAddressDetails(name, value, { validationRegexp: regexp }) - } - /> -
-
-
-
- -
- - handleAddressDetails(name, value, { - validationRegexp: "^.{1,255}$", - }) - } - /> -
-
-
- ); -}; - -export default AddressDetails; diff --git a/app/javascript/src/components/Profile/BankAccountDetails/BankDetails.tsx b/app/javascript/src/components/Profile/BankAccountDetails/BankDetails.tsx deleted file mode 100644 index 9fb943399f..0000000000 --- a/app/javascript/src/components/Profile/BankAccountDetails/BankDetails.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import React, { useEffect } from "react"; - -import { XIcon, ShieldSVG } from "miruIcons"; -import { isEmpty } from "ramda"; - -import wiseApi from "apis/wise"; - -import AddressDetails from "./AddressDetails"; -import BankDetailInput from "./BillingDetailInput"; - -const BankDetails = ({ - bankRequirements, - firstName, - setFirstName, - lastName, - setLastName, - recipientDetails, - setRecipientDetails, - validRecipientDetails, - setValidRecipientDetails, - setBankDetailsModal, - submitBankDetails, -}) => { - useEffect(() => { - setRecipientDetails({ - ...recipientDetails, - type: bankRequirements[0]["type"], - }); - }, []); - - const updateDetails = (isValid, key, value) => { - const valid = { ...validRecipientDetails }; - const details = { ...recipientDetails }; - - valid["details"][`${key}`] = isValid; - details["details"][`${key}`] = value; - setRecipientDetails(details); - setValidRecipientDetails(valid); - }; - - const updateAddressDetails = (isValid, key, value) => { - const valid = { ...validRecipientDetails }; - const details = { ...recipientDetails }; - - valid["details"]["address"][`${key}`] = isValid; - details["details"]["address"][`${key}`] = value; - setRecipientDetails(details); - setValidRecipientDetails(valid); - }; - - const handleBankDetails = async (key, value, fieldDetails) => { - const validationAsync = fieldDetails["validationAsync"]; - - if (validationAsync) { - try { - const url = `${validationAsync.url}?${validationAsync.params[0]["key"]}=${value}`; - await wiseApi.validateAccountDetail(url); - updateDetails(true, key, value); - } catch (error) { - if (error.response.status == 400) { - updateDetails(false, key, value); - } - } - } else if (fieldDetails["validationRegexp"]) { - value.match(new RegExp(fieldDetails["validationRegexp"])) - ? updateDetails(true, key, value) - : updateDetails(false, key, value); - } else { - updateDetails(true, key, value); - } - }; - - const handleAddressDetails = (key, value, fieldDetails) => { - if (fieldDetails["validationRegexp"]) { - value.match(new RegExp(fieldDetails["validationRegexp"])) - ? updateAddressDetails(true, key, value) - : updateAddressDetails(false, key, value); - } else { - updateAddressDetails(true, key, value); - } - }; - - const isFormValid = - Object.values(validRecipientDetails["details"]).every(value => value) && - Object.values(validRecipientDetails["details"]["address"]).every( - value => value - ) && - !isEmpty(firstName) && - !isEmpty(lastName); - - return ( - <> -
-
- {/*content*/} -
- {/*header*/} -
-

Enter Bank Details

- -
- {/*Info*/} -
- -

- We don't store your bank details on our servers to ensure - security of your information -

-
- {/*body*/} -
- -
- setFirstName(value)} - /> - setLastName(value)} - /> -
- {bankRequirements[0]["fields"].map(field => ( - - ))} - -
- {/*footer*/} -
- -
-
-
-
-
- - ); -}; - -export default BankDetails; diff --git a/app/javascript/src/components/Profile/BankAccountDetails/BankInfo.tsx b/app/javascript/src/components/Profile/BankAccountDetails/BankInfo.tsx deleted file mode 100644 index 4a6abdddbc..0000000000 --- a/app/javascript/src/components/Profile/BankAccountDetails/BankInfo.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useEffect } from "react"; - -import Logger from "js-logger"; - -import wiseApi from "apis/wise"; - -const BankInfo = ({ - recipientId, - sourceCurrency, - targetCurrency, - fetchAccountRequirements, - setBankDetailsModal, - setIsLoading, - setFirstName, - setLastName, - setRecipientDetails, - setIsUpdate, -}) => { - useEffect(() => { - fetchRecipientDetails(recipientId); - }, []); - - useEffect(() => { - fetchAccountRequirements(sourceCurrency, targetCurrency, true); - }, []); - - const fetchRecipientDetails = async recipientId => { - try { - setIsLoading(true); - const response = await wiseApi.fetchRecipient(recipientId); - const name = response.data["accountHolderName"].split(" "); - setRecipientDetails(response.data); - setFirstName(name.shift()); - setLastName(name.join(" ")); - setIsUpdate(true); - } catch (error) { - Logger.error(error); - } finally { - setIsLoading(false); - } - }; - - return ( -
-

You have already submitted your bank details.

- -
- ); -}; - -export default BankInfo; diff --git a/app/javascript/src/components/Profile/BankAccountDetails/BillingDetailInput.tsx b/app/javascript/src/components/Profile/BankAccountDetails/BillingDetailInput.tsx deleted file mode 100644 index b436082409..0000000000 --- a/app/javascript/src/components/Profile/BankAccountDetails/BillingDetailInput.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from "react"; - -const BillingDetailInput = ({ field, handleBankDetails, recipientDetails }) => { - const group = field["group"][0]; - - if (group.type == "text") { - return ( -
- -
- - handleBankDetails(name, value, group) - } - /> -
-
- ); - } - - return ( -
- -
- -
-
- ); -}; - -export default BillingDetailInput; diff --git a/app/javascript/src/components/Profile/BankAccountDetails/CurrencyDropdown.tsx b/app/javascript/src/components/Profile/BankAccountDetails/CurrencyDropdown.tsx deleted file mode 100644 index 44f0c31f7b..0000000000 --- a/app/javascript/src/components/Profile/BankAccountDetails/CurrencyDropdown.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { useEffect } from "react"; - -import getSymbolFromCurrency from "currency-symbol-map"; -import Logger from "js-logger"; - -import wiseApi from "apis/wise"; - -const CurrencyDropdown = ({ - currencies, - setCurrencies, - currency, - setCurrency, - setIsLoading, - setBankDetailsModal, - fetchAccountRequirements, -}) => { - useEffect(() => { - fetchCurrencyList(); - }, []); - - useEffect(() => { - if (currency) { - fetchAccountRequirements("USD", currency); - } - }, [currency]); - - const fetchCurrencyList = async () => { - try { - setIsLoading(true); - const response = await wiseApi.fetchCurrencies(); - const list = response.data.map(currency => ({ - label: `${currency} (${getSymbolFromCurrency(currency)})`, - value: currency, - })); - setCurrencies(list); - } catch (error) { - Logger.error(error); - } finally { - setIsLoading(false); - } - }; - - return ( -
-
- -
-
- - -
-
- ); -}; - -export default CurrencyDropdown; diff --git a/app/javascript/src/components/Profile/BankAccountDetails/index.tsx b/app/javascript/src/components/Profile/BankAccountDetails/index.tsx deleted file mode 100644 index 8874d8e94a..0000000000 --- a/app/javascript/src/components/Profile/BankAccountDetails/index.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import React, { useState, useEffect } from "react"; - -import { - bankFieldValidationRequirements, - separateAddressFields, -} from "helpers"; -import Logger from "js-logger"; -import { isEmpty } from "ramda"; - -import profilesApi from "apis/profiles"; -import wiseApi from "apis/wise"; -import Loader from "common/Loader"; -import { sendGAPageView } from "utils/googleAnalytics"; - -import BankDetails from "./BankDetails"; -import BankInfo from "./BankInfo"; -import CurrencyDropdown from "./CurrencyDropdown"; - -import Header from "../Header"; - -const BankAccountDetails = () => { - const [isUpdate, setIsUpdate] = useState(); - const [billingDetails, setBillingDetails] = useState({}); - const [currencies, setCurrencies] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [currency, setCurrency] = useState(); - const [bankDetailsModal, setBankDetailsModal] = useState(false); - const [bankRequirements, setBankRequirements] = useState(); - const [recipientDetails, setRecipientDetails] = useState({ - details: { address: {} }, - }); - - const [validRecipientDetails, setValidRecipientDetails] = useState({ - details: { address: {} }, - }); - - const [firstName, setFirstName] = useState(); - const [lastName, setLastName] = useState(); - - useEffect(() => { - sendGAPageView(); - fetchProfileDetails(); - }, []); - - const fetchProfileDetails = async () => { - try { - setIsLoading(true); - const response = await profilesApi.get(); - setBillingDetails(response.data); - } catch (error) { - Logger.error(error); - } finally { - setIsLoading(false); - } - }; - - const createRecipient = async payload => { - try { - const response = await wiseApi.createRecipient(payload); - const billingResponse = await profilesApi.post(response.data); - setBillingDetails(billingResponse.data); - } catch { - setBankDetailsModal(true); - throw new Error("Error while creating recipient"); - } finally { - setIsLoading(false); - } - }; - - const updateRecipient = async payload => { - try { - const response = await wiseApi.updateRecipient(payload); - const billingResponse = await profilesApi.put( - billingDetails.id, - response.data - ); - setBillingDetails(billingResponse.data); - setRecipientDetails(response.data); - } catch { - setBankDetailsModal(true); - throw new Error("Error while creating recipient"); - } finally { - setIsLoading(false); - } - }; - - // TODO: Try to remove duplicate code as much as possible and optimize - const fetchAccountRequirements = async ( - sourceCurrency, - targetCurrency, - isUpdate = false - ) => { - try { - setIsLoading(true); - const response = await wiseApi.fetchAccountRequirements( - sourceCurrency, - targetCurrency - ); - const data = response.data; - const validRecipientDetails = bankFieldValidationRequirements( - data, - isUpdate - ); - setValidRecipientDetails(validRecipientDetails); - if (!isUpdate) { - setRecipientDetails({ details: { address: {} } }); - } - - const fields = data.map(requirement => - separateAddressFields(requirement) - ); - setBankRequirements(fields); - } catch (error) { - Logger.error(error); - } finally { - setIsLoading(false); - } - }; - - const submitBankDetails = () => { - const payload = { - ...recipientDetails, - accountHolderName: `${firstName} ${lastName}`, - currency: currency || billingDetails.targetCurrency, - }; - setIsLoading(true); - setBankDetailsModal(false); - isUpdate ? updateRecipient(payload) : createRecipient(payload); - }; - - const renderBankDetails = billingDetails => { - if (isEmpty(billingDetails)) { - return
; - } else if (!billingDetails.recipientId) { - return ( - - ); - } - - return ( - - ); - }; - - return ( -
-
-
- {isLoading && } -
Bank Account Details
- {renderBankDetails(billingDetails)} - {bankDetailsModal && ( - - )} -
-
- ); -}; - -export default BankAccountDetails; diff --git a/app/javascript/src/components/Profile/Billing/Table/TableHeader.tsx b/app/javascript/src/components/Profile/Billing/Table/TableHeader.tsx deleted file mode 100644 index b3612c9fb2..0000000000 --- a/app/javascript/src/components/Profile/Billing/Table/TableHeader.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from "react"; - -const TableHeader = () => ( - - - DATE - - - DESCRIPTION - - - TEAM MEMBERS - - - TOTAL BILL AMT - - - PAYMENT TYPE - - - -); - -export default TableHeader; diff --git a/app/javascript/src/components/Profile/Billing/Table/TableRow.tsx b/app/javascript/src/components/Profile/Billing/Table/TableRow.tsx deleted file mode 100644 index 80e931dc42..0000000000 --- a/app/javascript/src/components/Profile/Billing/Table/TableRow.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useState } from "react"; - -import { DownloadSimpleIcon } from "miruIcons"; - -const TableRow = ({ data }) => { - const [isSending, setIsSending] = useState(false); - - return ( - - - {data.date} - - - {data.description} - - - {data.team_members} - - - {data.total_bill_amt} - - - {data.payment_type} - - -
- -
- - - ); -}; - -export default TableRow; diff --git a/app/javascript/src/components/Profile/Billing/Table/index.tsx b/app/javascript/src/components/Profile/Billing/Table/index.tsx deleted file mode 100644 index b3def7b154..0000000000 --- a/app/javascript/src/components/Profile/Billing/Table/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; - -import TableHeader from "./TableHeader"; -import TableRow from "./TableRow"; - -const data = []; - -const Table = () => ( - - - - - - {data.map((data, index) => ( - - ))} - -
-); - -export default Table; diff --git a/app/javascript/src/components/Profile/Billing/index.tsx b/app/javascript/src/components/Profile/Billing/index.tsx deleted file mode 100644 index eb3acbf21a..0000000000 --- a/app/javascript/src/components/Profile/Billing/index.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from "react"; - -import { Divider } from "common/Divider"; - -import Table from "./Table"; - -import Header from "../Header"; - -const Billing = () => ( -
-
-
-
-
-
Next Billing Date
-
-
-
- -
-
-

- Add-Ons -

-

1 team member

-

- -$ per user per month -

-
-
-

-$/mo

-

- charged every month{" "} -

-
-
- -
-
Total
-
-

-$

-

- plus taxes{" "} -

-
-
-
-
-

Billing History

- - - - -); - -export default Billing; diff --git a/app/javascript/src/components/Profile/Common/DetailsHeader.tsx b/app/javascript/src/components/Profile/Common/DetailsHeader.tsx new file mode 100644 index 0000000000..0fcb2dd2a4 --- /dev/null +++ b/app/javascript/src/components/Profile/Common/DetailsHeader.tsx @@ -0,0 +1,53 @@ +import React from "react"; + +import { getYear } from "date-fns"; + +import CustomYearPicker from "common/CustomYearPicker"; + +const DetailsHeader = ({ + title, + subTitle, + showButtons = false, + editAction, + isDisableUpdateBtn = false, + showYearPicker = false, + currentYear = getYear(new Date()), + setCurrentYear, +}: Iprops) => ( +
+ {title} + {subTitle && {subTitle}} + {showYearPicker && ( + + )} +
+
+ +
+
+
+); + +interface Iprops { + title: string; + subTitle: string; + showButtons?: boolean; + editAction?: () => any; + isDisableUpdateBtn?: boolean; + showYearPicker?: boolean; + currentYear?: number; + setCurrentYear?: () => any; +} + +export default DetailsHeader; diff --git a/app/javascript/src/components/Profile/Common/EditHeader.tsx b/app/javascript/src/components/Profile/Common/EditHeader.tsx new file mode 100644 index 0000000000..e6fb76f331 --- /dev/null +++ b/app/javascript/src/components/Profile/Common/EditHeader.tsx @@ -0,0 +1,78 @@ +import React from "react"; + +import { getYear } from "date-fns"; +import { XIcon } from "miruIcons"; + +import CustomYearPicker from "common/CustomYearPicker"; + +const EditHeader = ({ + title, + subTitle, + showButtons = false, + cancelAction, + saveAction, + isDisableUpdateBtn = false, + showYearPicker = false, + currentYear = getYear(new Date()), + setCurrentYear, +}: Iprops) => ( + <> +
+

{title}

+ {subTitle && {subTitle}} + {showYearPicker && ( + + )} +
+
+ + +
+
+
+
+

+ {title} +

+
+ +
+
+ +); + +interface Iprops { + title: string; + subTitle: string; + showButtons?: boolean; + cancelAction?: () => any; + saveAction?: () => any; + isDisableUpdateBtn?: boolean; + showYearPicker?: boolean; + currentYear?: number; + setCurrentYear?: () => any; +} + +export default EditHeader; diff --git a/app/javascript/src/components/Profile/CommonComponents/Header/index.tsx b/app/javascript/src/components/Profile/CommonComponents/Header/index.tsx deleted file mode 100644 index 1621d1400a..0000000000 --- a/app/javascript/src/components/Profile/CommonComponents/Header/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -const Header = () => ( -
Profile & Settings
-); - -export default React.memo(Header); diff --git a/app/javascript/src/components/Profile/context/CompensationDetailsState.tsx b/app/javascript/src/components/Profile/Context/CompensationDetailsState.tsx similarity index 100% rename from app/javascript/src/components/Profile/context/CompensationDetailsState.tsx rename to app/javascript/src/components/Profile/Context/CompensationDetailsState.tsx diff --git a/app/javascript/src/components/Profile/context/EmploymentDetailsState.tsx b/app/javascript/src/components/Profile/Context/EmploymentDetailsState.tsx similarity index 100% rename from app/javascript/src/components/Profile/context/EmploymentDetailsState.tsx rename to app/javascript/src/components/Profile/Context/EmploymentDetailsState.tsx diff --git a/app/javascript/src/components/Profile/context/PersonalDetailsState.tsx b/app/javascript/src/components/Profile/Context/PersonalDetailsState.tsx similarity index 97% rename from app/javascript/src/components/Profile/context/PersonalDetailsState.tsx rename to app/javascript/src/components/Profile/Context/PersonalDetailsState.tsx index 3695068aa7..585967581c 100644 --- a/app/javascript/src/components/Profile/context/PersonalDetailsState.tsx +++ b/app/javascript/src/components/Profile/Context/PersonalDetailsState.tsx @@ -1,4 +1,5 @@ export const PersonalDetailsState = { + id: "", first_name: "", last_name: "", date_of_birth: "", diff --git a/app/javascript/src/components/Profile/DetailsHeader.tsx b/app/javascript/src/components/Profile/DetailsHeader.tsx deleted file mode 100644 index bf845db9bc..0000000000 --- a/app/javascript/src/components/Profile/DetailsHeader.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useState } from "react"; - -import { getYear } from "date-fns"; - -import CustomYearPicker from "common/CustomYearPicker"; - -const DetailsHeader = ({ - title, - subTitle, - showButtons = false, - editAction, - isDisableUpdateBtn = false, - showYearPicker = false, -}: Iprops) => { - const [currentYear, setCurrentYear] = useState(getYear(new Date())); - - return ( -
- {title} - {subTitle} - {showYearPicker && ( - - )} -
-
- -
-
-
- ); -}; - -interface Iprops { - title: string; - subTitle: string; - showButtons?: boolean; - editAction?: () => any; - isDisableUpdateBtn?: boolean; - showYearPicker?: boolean; -} - -export default DetailsHeader; diff --git a/app/javascript/src/components/Profile/GoogleCalendar/Header.tsx b/app/javascript/src/components/Profile/GoogleCalendar/Header.tsx deleted file mode 100644 index 7095040695..0000000000 --- a/app/javascript/src/components/Profile/GoogleCalendar/Header.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from "react"; - -const Header = ({ title }: Iprops) => ( - <> -
- {title} -
-
-

- {title} -

-
- -); - -interface Iprops { - title: string; -} - -export default Header; diff --git a/app/javascript/src/components/Profile/GoogleCalendar/index.tsx b/app/javascript/src/components/Profile/GoogleCalendar/index.tsx deleted file mode 100644 index cab76b88fb..0000000000 --- a/app/javascript/src/components/Profile/GoogleCalendar/index.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React, { useEffect, useState } from "react"; - -import Logger from "js-logger"; -import { GoogleCalendarIcon, IntegrateIcon } from "miruIcons"; -import { Button, Switch } from "StyledComponents"; - -import companiesApi from "apis/companies"; -import googleCalendarApi from "apis/googleCalendar"; -import teamApi from "apis/team"; -import Loader from "common/Loader/index"; -import { useUserContext } from "context/UserContext"; - -import Header from "./Header"; - -const GoogleCalendar = () => { - const { - isAdminUser: isAdmin, - calendarEnabled, - calendarConnected, - } = useUserContext(); - - const [connectGoogleCalendar, setConnectGoogleCalendar] = - useState(calendarConnected); - const [enabled, setEnabled] = useState(calendarEnabled); - const [apiCallNeeded, setApiCallNeeded] = useState(false); - const [loading, setLoading] = useState(false); - - useEffect(() => { - if (isAdmin) { - const companiesData = async () => { - setLoading(true); - try { - const { - data: { company_details }, - } = await companiesApi.index(); - setEnabled(company_details.calendar_enabled); - } catch (error) { - Logger.log(error); - } finally { - setLoading(false); - } - }; - companiesData(); - } - }, []); - - useEffect(() => { - if (apiCallNeeded) { - enableCalendar(); - setApiCallNeeded(false); - } - }, [apiCallNeeded]); - - if (loading) { - return ; - } - - const enableCalendar = async () => { - try { - const payload = { team: { calendar_enabled: enabled } }; - await teamApi.updateTeamMembers(payload); - } catch (error) { - Logger.log(error); - } - }; - - const toggleEnabled = () => { - setEnabled(prevEnabled => !prevEnabled); - setApiCallNeeded(true); - }; - - const handleConnectCalendar = async () => { - setConnectGoogleCalendar(true); - const { data } = await googleCalendarApi.redirect(); - window.location.replace(data.url); - }; - - const handleDisconnectCalendar = async () => { - setConnectGoogleCalendar(false); - await googleCalendarApi.callback(); - window.location.replace("/settings/integrations"); - }; - - const showConnectDisconnectBtn = () => { - if (enabled) { - return connectGoogleCalendar ? ( -
- - -
- ) : ( - - ); - } - }; - - return ( -
-
-
-
-
-
-
- -
- {isAdmin && } -
- - Google Calendar - -

- Connect your google calendar to automatically sync your meetings - with Miru -

- {showConnectDisconnectBtn()} -
-
-
-
- ); -}; - -export default GoogleCalendar; diff --git a/app/javascript/src/components/Profile/Header.tsx b/app/javascript/src/components/Profile/Header.tsx deleted file mode 100644 index 2e583ab0c4..0000000000 --- a/app/javascript/src/components/Profile/Header.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React, { useState } from "react"; - -import { getYear } from "date-fns"; -import { XIcon } from "miruIcons"; - -import CustomYearPicker from "common/CustomYearPicker"; - -const Header = ({ - title, - subTitle, - showButtons = false, - cancelAction, - saveAction, - isDisableUpdateBtn = false, - showYearPicker = false, -}: Iprops) => { - const [currentYear, setCurrentYear] = useState(getYear(new Date())); - - return ( - <> -
- {title} - {subTitle} - {showYearPicker && ( - - )} -
-
- - -
-
-
-
-

- {title} -

-
- -
-
- - ); -}; - -interface Iprops { - title: string; - subTitle: string; - showButtons?: boolean; - cancelAction?: () => any; - saveAction?: () => any; - isDisableUpdateBtn?: boolean; - showYearPicker?: boolean; -} - -export default Header; diff --git a/app/javascript/src/components/Profile/Layout.tsx b/app/javascript/src/components/Profile/Layout.tsx deleted file mode 100644 index 90c5dffcd0..0000000000 --- a/app/javascript/src/components/Profile/Layout.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/* eslint-disable no-unused-vars */ -import React, { Fragment, useState } from "react"; - -import { useUserContext } from "context/UserContext"; - -import Header from "./CommonComponents/Header"; -import { CompensationDetailsState } from "./context/CompensationDetailsState"; -import { EmploymentDetailsState } from "./context/EmploymentDetailsState"; -import EntryContext from "./context/EntryContext"; -import { PersonalDetailsState } from "./context/PersonalDetailsState"; -import RouteConfig from "./RouteConfig"; -import SubNav from "./SubNav"; - -const Layout = ({ isAdminUser, user, company }) => { - const { isDesktop } = useUserContext(); - - const [settingsStates, setSettingsStates] = useState({ - profileSettings: PersonalDetailsState, - employmentDetails: EmploymentDetailsState, - compensationDetails: CompensationDetailsState, - organizationSettings: {}, - bankAccDetails: {}, - paymentSettings: {}, - billing: {}, - }); - - const { profileSettings, employmentDetails } = settingsStates; - const setUserState = (key, value) => { - setSettingsStates(previousSettings => ({ - ...previousSettings, - ...{ [key]: { ...previousSettings[key], ...value } }, - })); - }; - - return ( - - {isDesktop && ( - -
-
-
-
-
- -
-
- -
-
-
- )} - {!isDesktop && } -
- ); -}; - -export default Layout; diff --git a/app/javascript/src/components/Profile/Layout/Header.tsx b/app/javascript/src/components/Profile/Layout/Header.tsx new file mode 100644 index 0000000000..5f9e952013 --- /dev/null +++ b/app/javascript/src/components/Profile/Layout/Header.tsx @@ -0,0 +1,41 @@ +import React from "react"; + +import { ArrowLeftIcon } from "miruIcons"; +import { useNavigate } from "react-router-dom"; +import { Button } from "StyledComponents"; + +import { useProfileContext } from "context/Profile/ProfileContext"; + +const Header = () => { + const navigate = useNavigate(); + const { personalDetails, isCalledFromSettings } = useProfileContext(); + + const getHeaderContent = () => { + if (isCalledFromSettings) { + return Profile & Settings; + } + + return ( +
+ + + {`${personalDetails.first_name} ${personalDetails.last_name}`} + +
+ ); + }; + + return ( +
+ {getHeaderContent()} +
+ ); +}; + +export default Header; diff --git a/app/javascript/src/components/Profile/Layout/MobileNav.tsx b/app/javascript/src/components/Profile/Layout/MobileNav.tsx deleted file mode 100644 index 18a9e2222d..0000000000 --- a/app/javascript/src/components/Profile/Layout/MobileNav.tsx +++ /dev/null @@ -1,160 +0,0 @@ -/* eslint-disable no-unused-vars */ - -import React, { useEffect, useState } from "react"; - -import { - ClientsIcon as BuildingsIcon, - UserIcon, - PaymentsIcon, - ProjectsIcon, - MobileIcon, -} from "miruIcons"; -import { Outlet, useNavigate, useParams } from "react-router-dom"; - -import WorkspaceApi from "apis/workspaces"; -import Loader from "common/Loader/index"; -import withLayout from "common/Mobile/HOC/withLayout"; -import { useUserContext } from "context/UserContext"; - -import { TeamUrl } from "./TeamUrl"; -import { UserDetails } from "./UserDetails"; - -const getSettingsNavUrls = memberId => [ - { - groupName: "Personal", - navItems: [ - { - url: "/settings/profile", - text: "PERSONAL DETAILS", - icon: , - }, - { - url: "/settings/employment", - text: "EMPLOYMENT DETAILS", - icon: , - }, - { - url: "/settings/devices", - text: "ALLOCATED DEVICES", - icon: , - }, - //Todo: Uncomment while API integration - // { - // url: "/settings/compensation", - // text: "COMPENSATION", - // icon: , - // }, - ], - }, - - { - isCompanyDetails: true, - navItems: [ - { - url: "/settings/organization", - text: "ORG. SETTINGS", - icon: , - }, - { - url: "/settings/payment", - text: "PAYMENT SETTINGS", - icon: , - }, - // { - // url: "/settings/leaves", - // text: "Leaves", - // icon: , - // }, - // { - // url: "/settings/holidays", - // text: "Holidays", - // icon: , - // }, - ], - }, -]; - -const getEmployeeSettingsNavUrls = memberId => [ - { - groupName: "Personal", - navItems: [ - { - url: "/settings/profile", - text: "PERSONAL DETAILS", - icon: , - }, - { - url: "/settings/employment", - text: "EMPLOYMENT DETAILS", - icon: , - }, - { - url: "/settings/devices", - text: "ALLOCATED DEVICES", - icon: , - }, - { - url: "/settings/compensation", - text: "COMPENSATION", - icon: , - }, - ], - }, -]; - -const MobileNav = () => { - const { isAdminUser: isAdmin, isDesktop, user } = useUserContext(); - const { memberId } = useParams(); - const AdminUrlList = getSettingsNavUrls(memberId); - const EmployeeUrlList = getEmployeeSettingsNavUrls(memberId); - const navigate = useNavigate(); - const [currentWorkspace, setCurrentWorkspace] = useState({}); - const [isLoading, setIsLoading] = useState(false); - - const getCurrentWorkspace = async () => { - const res = await WorkspaceApi.get(); - const { workspaces } = res.data; - workspaces.find(wrk => { - if (wrk.id == user.current_workspace_id) { - setCurrentWorkspace(wrk); - } - }); - setIsLoading(false); - }; - - useEffect(() => { - setIsLoading(true); - getCurrentWorkspace(); - }, []); - - useEffect(() => { - if (isDesktop) { - navigate("/settings/profile"); - } - }, [isDesktop]); - - const mobileView = () => ( -
- - - -
- ); - - const DisplayView = withLayout(mobileView, true, true); - - if (isLoading) { - return ( -
- -
- ); - } - - return ; -}; - -export default MobileNav; diff --git a/app/javascript/src/components/Profile/Layout/NavItem.tsx b/app/javascript/src/components/Profile/Layout/NavItem.tsx deleted file mode 100644 index 7f4690f6eb..0000000000 --- a/app/javascript/src/components/Profile/Layout/NavItem.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from "react"; - -import { RightArrowIcon } from "miruIcons"; - -interface NavItemProps { - icon?: any; - text: string; - url: string; -} - -const NavItem = ({ icon, text, url }: NavItemProps) => ( - -
  • -
    -
    {icon}
    -

    - {text} -

    -
    -
    - -
    -
  • -
    -); - -export default NavItem; diff --git a/app/javascript/src/components/Profile/Layout/Navigation/AdminNav.tsx b/app/javascript/src/components/Profile/Layout/Navigation/AdminNav.tsx new file mode 100644 index 0000000000..a1c082a68f --- /dev/null +++ b/app/javascript/src/components/Profile/Layout/Navigation/AdminNav.tsx @@ -0,0 +1,62 @@ +import React, { Fragment, useState } from "react"; + +import { MinusIcon, PlusIcon } from "miruIcons"; + +import { useUserContext } from "context/UserContext"; + +import List from "./List"; + +import { SETTINGS } from "../routes"; + +const AdminNav = () => { + const { companyRole, company } = useUserContext(); + const [openedSubNav, setOpenedSubNav] = useState({ + personal: true, + organization: false, + }); + + const toggleSection = section => { + setOpenedSubNav({ + personal: section === "personal", + organization: section === "organization", + }); + }; + + const personalSettings = SETTINGS.filter( + ({ category }) => category === "personal" + ); + + const organizationalSettings = SETTINGS.filter( + ({ category }) => category === "organization" + ); + + const renderSection = (title, section, settingsList) => ( + +
    toggleSection(section)} + > + {title} +
    + {openedSubNav[section] ? ( + + ) : ( + + )} +
    +
    + {openedSubNav[section] && ( + + )} +
    + ); + + return ( +
    + {renderSection("Personal", "personal", personalSettings)} + {renderSection(company.name, "organization", organizationalSettings)} +
    + ); +}; + +export default AdminNav; diff --git a/app/javascript/src/components/Profile/Layout/Navigation/List.tsx b/app/javascript/src/components/Profile/Layout/Navigation/List.tsx new file mode 100644 index 0000000000..d28445adeb --- /dev/null +++ b/app/javascript/src/components/Profile/Layout/Navigation/List.tsx @@ -0,0 +1,34 @@ +import React from "react"; + +import { NavLink } from "react-router-dom"; + +const getActiveClassName = isActive => { + if (isActive) { + return "pl-4 py-5 border-l-8 border-miru-han-purple-600 bg-miru-gray-200 text-miru-han-purple-600 block w-full flex items-center"; + } + + return "pl-6 py-5 border-b-1 border-miru-gray-400 block w-full flex items-center"; +}; + +const List = ({ settingsList, companyRole }) => ( +
      + {settingsList.map((setting, index) => { + if (setting.isTab && setting.authorisedRoles.includes(companyRole)) { + return ( +
    • + getActiveClassName(isActive)} + to={setting.path} + > + {setting.icon} + {setting.label} + +
    • + ); + } + })} +
    +); + +export default List; diff --git a/app/javascript/src/components/Profile/Layout/Navigation/MobileNav.tsx b/app/javascript/src/components/Profile/Layout/Navigation/MobileNav.tsx new file mode 100644 index 0000000000..9673d0ed9c --- /dev/null +++ b/app/javascript/src/components/Profile/Layout/Navigation/MobileNav.tsx @@ -0,0 +1,92 @@ +/* eslint-disable no-unused-vars */ + +import React, { Fragment, useEffect, useState } from "react"; + +import { Outlet, useNavigate } from "react-router-dom"; + +import WorkspaceApi from "apis/workspaces"; +import Loader from "common/Loader/index"; +import withLayout from "common/Mobile/HOC/withLayout"; +import { useUserContext } from "context/UserContext"; + +import List from "./List"; +import UserInformation from "./UserInformation"; + +import { SETTINGS } from "../routes"; + +const MobileNav = () => { + const { companyRole, isDesktop, user, isAdminUser } = useUserContext(); + const navigate = useNavigate(); + const [currentWorkspace, setCurrentWorkspace] = useState({}); + const [isLoading, setIsLoading] = useState(false); + + const personalSettings = SETTINGS.filter( + ({ category }) => category === "personal" + ); + + const organizationalSettings = SETTINGS.filter( + ({ category }) => category === "organization" + ); + + const getCurrentWorkspace = async () => { + const res = await WorkspaceApi.get(); + const { workspaces } = res.data; + workspaces.find(wrk => { + if (wrk.id == user.current_workspace_id) { + setCurrentWorkspace(wrk); + } + }); + setIsLoading(false); + }; + + const renderSection = (title, settingsList) => ( + +
    + {title} +
    + +
    + ); + + useEffect(() => { + setIsLoading(true); + getCurrentWorkspace(); + }, []); + + useEffect(() => { + if (isDesktop) { + navigate("/settings/profile"); + } + }, [isDesktop]); + + const mobileView = () => ( +
    + +
    + {isAdminUser ? ( + + {renderSection("Personal", personalSettings)} + {renderSection(currentWorkspace.name, organizationalSettings)} + + ) : ( + {renderSection("Personal", personalSettings)} + )} +
    + +
    + ); + + const DisplayView = withLayout(mobileView, true, true); + + if (isLoading) { + return ( +
    + +
    + ); + } + + return ; +}; + +export default MobileNav; diff --git a/app/javascript/src/components/Profile/CommonComponents/UserInformation/index.tsx b/app/javascript/src/components/Profile/Layout/Navigation/UserInformation.tsx similarity index 68% rename from app/javascript/src/components/Profile/CommonComponents/UserInformation/index.tsx rename to app/javascript/src/components/Profile/Layout/Navigation/UserInformation.tsx index a35c0813e1..a233111961 100644 --- a/app/javascript/src/components/Profile/CommonComponents/UserInformation/index.tsx +++ b/app/javascript/src/components/Profile/Layout/Navigation/UserInformation.tsx @@ -1,14 +1,34 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; -import { EditIcon, ImageIcon, DeleteIcon } from "miruIcons"; -import { Avatar, MoreOptions, Toastr, Tooltip } from "StyledComponents"; +import { UserAvatarSVG, DeleteIcon, ImageIcon, EditIcon } from "miruIcons"; +import { MoreOptions, Toastr, Tooltip } from "StyledComponents"; -import profileApi from "apis/profile"; -import { useUserContext } from "context/UserContext"; +import teamApi from "apis/team"; +import teamsApi from "apis/teams"; +import { useProfileContext } from "context/Profile/ProfileContext"; + +const UserInformation = () => { + const { + personalDetails: { first_name, last_name, id }, + } = useProfileContext(); -const UserInformation = ({ firstName, lastName, designation }) => { const [showProfileOptions, setShowProfileOptions] = useState(false); - const { avatarUrl, setCurrentAvatarUrl } = useUserContext(); + const [imageUrl, setImageUrl] = useState(null); + + const getAvatar = async () => { + try { + const responseData = await teamsApi.get(id); + setImageUrl(responseData.data.avatar_url); + } catch { + Toastr.error("Error in getting Profile Image"); + } + }; + + useEffect(() => { + if (id) { + getAvatar(); + } + }, [id]); const validateFileSize = file => { const sizeInKB = file.size / 1024; @@ -29,23 +49,25 @@ const UserInformation = ({ firstName, lastName, designation }) => { setShowProfileOptions(false); const file = e.target.files[0]; validateFileSize(file); - setCurrentAvatarUrl(URL.createObjectURL(file)); + setImageUrl(URL.createObjectURL(file)); const payload = createFormData(file); - const headers = { "Content-Type": "multipart/form-data", }; - - await profileApi.upadteAvatar(payload, { headers }); + await teamApi.updateTeamMemberAvatar(id, payload, { headers }); } catch (error) { Toastr.error(error.message); } }; const handleDeleteProfileImage = async () => { - setShowProfileOptions(false); - await profileApi.removeAvatar(); - setCurrentAvatarUrl(null); + try { + setShowProfileOptions(false); + await teamApi.destroyTeamMemberAvatar(id); + setImageUrl(null); + } catch { + Toastr.error("Error in deleting Profile Image"); + } }; return ( @@ -54,7 +76,10 @@ const UserInformation = ({ firstName, lastName, designation }) => {
    - +
    -
    +
    {showProfileOptions && (
  • @@ -82,7 +107,7 @@ const UserInformation = ({ firstName, lastName, designation }) => { type="file" onChange={handleProfileImageChange} /> - {avatarUrl && ( + {imageUrl && (
  • { )}
    - {`${firstName} ${lastName}`} + {`${first_name} ${last_name}`} -

    - {designation} -

    diff --git a/app/javascript/src/components/Profile/Layout/Navigation/index.tsx b/app/javascript/src/components/Profile/Layout/Navigation/index.tsx new file mode 100644 index 0000000000..b70f435c6f --- /dev/null +++ b/app/javascript/src/components/Profile/Layout/Navigation/index.tsx @@ -0,0 +1,33 @@ +import React from "react"; + +import { useProfileContext } from "context/Profile/ProfileContext"; +import { useUserContext } from "context/UserContext"; + +import AdminNav from "./AdminNav"; +import List from "./List"; +import UserInformation from "./UserInformation"; + +import { SETTINGS } from "../routes"; + +const SideNav = () => { + const { isCalledFromSettings } = useProfileContext(); + const { isAdminUser, companyRole } = useUserContext(); + const personalSettings = SETTINGS.filter( + ({ category }) => category === "personal" + ); + + const EmployeeNav = () => ( + + ); + + return ( +
    + +
    + {isCalledFromSettings && isAdminUser ? : } +
    +
    + ); +}; + +export default SideNav; diff --git a/app/javascript/src/components/Team/Details/Layout/OutletWrapper.tsx b/app/javascript/src/components/Profile/Layout/OutletWrapper.tsx similarity index 100% rename from app/javascript/src/components/Team/Details/Layout/OutletWrapper.tsx rename to app/javascript/src/components/Profile/Layout/OutletWrapper.tsx diff --git a/app/javascript/src/components/Profile/Layout/RouteConfig.tsx b/app/javascript/src/components/Profile/Layout/RouteConfig.tsx new file mode 100644 index 0000000000..14226146cf --- /dev/null +++ b/app/javascript/src/components/Profile/Layout/RouteConfig.tsx @@ -0,0 +1,86 @@ +import React, { useEffect } from "react"; + +import { Routes, Route, Navigate } from "react-router-dom"; + +import ErrorPage from "common/Error"; +import Layout from "components/Profile/index"; +import { useProfileContext } from "context/Profile/ProfileContext"; +import { useUserContext } from "context/UserContext"; + +import { SETTINGS } from "./routes"; + +import UserDetailsView from "../Personal/User"; + +const ProtectedRoute = ({ role, authorisedRoles, children }) => { + if (authorisedRoles.includes(role)) { + return children; + } + + return ; +}; + +const RouteConfig = () => { + const { companyRole } = useUserContext(); + const { setIsCalledFromSettings, setIsCalledFromTeam } = useProfileContext(); + + useEffect(() => { + if (window.location.pathname.startsWith("/settings")) { + setIsCalledFromSettings(true); + } else { + setIsCalledFromSettings(false); + } + + if (window.location.pathname.startsWith("/team")) { + setIsCalledFromTeam(true); + } else { + setIsCalledFromTeam(false); + } + }, [window.location]); + + return ( + + {window.location.pathname.startsWith("/team") && ( + } path=":memberId"> + {SETTINGS.filter(({ category }) => category === "personal").map( + ({ path, authorisedRoles, Component }) => ( + + + + } + /> + ) + )} + + )} + {window.location.pathname.startsWith("/settings") && ( + } path="/*"> + } path="profile" /> + {SETTINGS.map(({ path, authorisedRoles, Component }) => ( + + + + } + /> + ))} + + )} + } path="*" /> + + ); +}; + +export default RouteConfig; diff --git a/app/javascript/src/components/Profile/Layout/TeamUrl.tsx b/app/javascript/src/components/Profile/Layout/TeamUrl.tsx deleted file mode 100644 index e8c8c16254..0000000000 --- a/app/javascript/src/components/Profile/Layout/TeamUrl.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from "react"; - -import NavItem from "./NavItem"; - -export const TeamUrl = ({ urlList, currentWorkspaceName }) => ( -
    -
      - {urlList.map((item, index) => ( -
    • - {item?.isCompanyDetails ? ( - <> -

      - {currentWorkspaceName} -

      -
        - {item?.navItems?.map(navItem => ( - - ))} -
      - - ) : ( - <> -

      - {item.groupName} -

      -
        - {item?.navItems?.map(navItem => ( - - ))} -
      - - )} -
    • - ))} -
    -
    -); diff --git a/app/javascript/src/components/Profile/Layout/UserDetails.tsx b/app/javascript/src/components/Profile/Layout/UserDetails.tsx deleted file mode 100644 index 383a53bed7..0000000000 --- a/app/javascript/src/components/Profile/Layout/UserDetails.tsx +++ /dev/null @@ -1,173 +0,0 @@ -/* eslint-disable no-unused-vars */ - -import React, { useEffect, useState } from "react"; - -import { DeleteIcon, EditIcon, ImageIcon } from "miruIcons"; -import { Avatar, MobileMoreOptions, Toastr, Tooltip } from "StyledComponents"; - -import profileApi from "apis/profile"; -import teamsApi from "apis/teams"; -import { useProfile } from "components/Profile/context/EntryContext"; -import { useUserContext } from "context/UserContext"; -import { employmentMapper, teamsMapper } from "mapper/teams.mapper"; - -export const UserDetails = () => { - const { setUserState, profileSettings, employmentDetails } = useProfile(); - const { first_name, last_name } = profileSettings; - const { - current_employment: { designation }, - } = employmentDetails; - - const [showImageUpdateOptions, setShowImageUpdateOptions] = - useState(false); - const { avatarUrl, setCurrentAvatarUrl, companyRole } = useUserContext(); - const getDetails = async () => { - try { - if (!first_name && !last_name) { - const userData = await profileApi.index(); - if (userData.status && userData.status == 200) { - const addressData = await profileApi.getAddress( - userData.data.user.id - ); - - const userObj = teamsMapper( - userData.data.user, - addressData.data.addresses[0] - ); - setUserState("profileSettings", userObj); - } - - if (companyRole !== "client" && !designation) { - const employmentData: any = await teamsApi.getEmploymentDetails( - userData.data.user.id - ); - - const previousEmploymentData = await teamsApi.getPreviousEmployments( - userData.data.user.id - ); - if (employmentData.status && employmentData.status == 200) { - const employmentObj = employmentMapper( - employmentData?.data?.employment, - previousEmploymentData?.data?.previous_employments - ); - setUserState("employmentDetails", employmentObj); - } - } - } - } catch { - Toastr.error("Something went wrong"); - } - }; - - const validateFileSize = file => { - const sizeInKB = file.size / 1024; - if (sizeInKB > 100) { - throw new Error("Image size needs to be less than 100 KB"); - } - }; - - const createFormData = file => { - const formData = new FormData(); - formData.append("user[avatar]", file); - - return formData; - }; - - const handleProfileImageChange = async e => { - try { - setShowImageUpdateOptions(false); - const file = e.target.files[0]; - validateFileSize(file); - setCurrentAvatarUrl(URL.createObjectURL(file)); - const payload = createFormData(file); - const headers = { - "Content-Type": "multipart/form-data", - }; - await profileApi.upadteAvatar(payload, { headers }); - } catch (error) { - Toastr.error(error.message); - } - }; - - const handleDeleteProfileImage = async () => { - setShowImageUpdateOptions(false); - await profileApi.removeAvatar(); - setCurrentAvatarUrl(null); - }; - - useEffect(() => { - getDetails(); - }, []); - - return ( -
    -
    -
    -
    - - -
    -
    - {showImageUpdateOptions ? ( - -
  • - - -
  • -
  • -
    - -
    -

    - Delete -

    -
  • - - ) : null} -
    - -
    - - {`${first_name} ${last_name}`} - -

    {designation}

    -
    -
    - -
    -
    -
    - ); -}; diff --git a/app/javascript/src/components/Profile/Layout/routes.tsx b/app/javascript/src/components/Profile/Layout/routes.tsx new file mode 100644 index 0000000000..8a80c076c5 --- /dev/null +++ b/app/javascript/src/components/Profile/Layout/routes.tsx @@ -0,0 +1,171 @@ +import React from "react"; + +import { + UserIcon, + ProjectsIcon, + MobileIcon, + PaymentsIcon, + CalendarIcon, + CakeIcon, + ClientsIcon, +} from "miruIcons"; + +import OrgDetails from "components/Profile/Organization/Details"; +import OrgEdit from "components/Profile/Organization/Edit"; +import Holidays from "components/Profile/Organization/Holidays"; +import Leaves from "components/Profile/Organization/Leaves"; +import PaymentSettings from "components/Profile/Organization/Payment"; +import AllocatedDevicesDetails from "components/Profile/Personal/Devices"; +import AllocatedDevicesEdit from "components/Profile/Personal/Devices/Edit"; +import EmploymentDetails from "components/Profile/Personal/Employment"; +import EmploymentDetailsEdit from "components/Profile/Personal/Employment/Edit"; +import UserDetailsView from "components/Profile/Personal/User"; +import UserDetailsEdit from "components/Profile/Personal/User/Edit"; +import { Roles } from "constants/index"; + +const { ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE, CLIENT } = Roles; + +export const SETTINGS = [ + { + label: "PROFILE SETTINGS", + path: "profile", + icon: , + authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE, CLIENT], + Component: UserDetailsView, + category: "personal", + isTab: true, + }, + { + label: "PROFILE SETTINGS", + path: "profile/edit", + icon: , + authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE, CLIENT], + Component: UserDetailsEdit, + category: "personal", + isTab: false, + }, + { + label: "EMPLOYMENT DETAILS", + path: "employment", + icon: , + authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE], + Component: EmploymentDetails, + category: "personal", + isTab: true, + }, + { + label: "EMPLOYMENT DETAILS", + path: "employment/edit", + icon: , + authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE], + Component: EmploymentDetailsEdit, + category: "personal", + isTab: false, + }, + { + label: "ALLOCATED DEVICES", + path: "devices", + icon: , + authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE], + Component: AllocatedDevicesDetails, + category: "personal", + isTab: true, + }, + { + label: "ALLOCATED DEVICES", + path: "devices/edit", + icon: , + authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE], + Component: AllocatedDevicesEdit, + category: "personal", + isTab: false, + }, + // Uncomment when Integrating with API + // { + // label: "COMPENSATION", + // path: "compensation", + // icon: , + // authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE], + // Component: CompensationDetails, + // category: "personal", + //isTab: false, + //}, + // { + // label: "COMPENSATION", + // path: "compensation/edit", + // icon: , + // authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE], + // Component: CompensationDetailsEdit, + // category: "personal", + // isTab: false, + //}, + { + label: "ORG. SETTINGS", + path: "organization", + icon: , + authorisedRoles: [ADMIN, OWNER], + Component: OrgDetails, + category: "organization", + isTab: true, + }, + { + label: "ORG. SETTINGS", + path: "organization/edit", + icon: , + authorisedRoles: [ADMIN, OWNER], + Component: OrgEdit, + category: "organization", + isTab: false, + }, + { + label: "PAYMENT SETTINGS", + path: "payment", + icon: , + authorisedRoles: [ADMIN, OWNER], + Component: PaymentSettings, + category: "organization", + isTab: true, + }, + { + label: "LEAVES", + path: "leaves", + icon: , + authorisedRoles: [ADMIN, OWNER], + Component: Leaves, + category: "organization", + isTab: true, + }, + { + label: "HOLIDAYS", + path: "holidays", + icon: , + authorisedRoles: [ADMIN, OWNER], + Component: Holidays, + category: "organization", + isTab: true, + }, + // Uncomment when Integrating with API + // { + // label: "Integration", + // path: "/integrations", + // icon: , + // authorisedRoles: [ADMIN, OWNER], + // Component: GoogleCalendar, + // category: "organization", + // isTab: false, + // }, + // { + // path: "/import", + // Component: OrganizationImport, + // authorisedRoles: [ADMIN, OWNER], + // category: "organization", + // isTab: false, + // }, + // { + // path: "/billing", + // Component: Billing, + // authorisedRoles: [ADMIN, OWNER], + // category: "organization", + //isTab: false, + // }, +]; diff --git a/app/javascript/src/components/Profile/Organization/Billing/index.tsx b/app/javascript/src/components/Profile/Organization/Billing/index.tsx index 622ec511f7..811ff5ab8b 100644 --- a/app/javascript/src/components/Profile/Organization/Billing/index.tsx +++ b/app/javascript/src/components/Profile/Organization/Billing/index.tsx @@ -5,14 +5,14 @@ import { sendGAPageView } from "utils/googleAnalytics"; import Table from "./Table"; -import Header from "../../Header"; +import EditHeader from "../../Common/EditHeader"; const Billing = () => { useEffect(() => sendGAPageView(), []); return (
    -
    { return (
    -
    ( -
    - Holidays - -
    -
    - -
    -
    -
    -); - -interface Iprops { - editAction?: () => any; - currentYear: any; - setCurrentYear: () => any; -} - -export default Header; diff --git a/app/javascript/src/components/Profile/Organization/Holidays/Details/index.tsx b/app/javascript/src/components/Profile/Organization/Holidays/Details/index.tsx index b6adc3d059..2c19731b2d 100644 --- a/app/javascript/src/components/Profile/Organization/Holidays/Details/index.tsx +++ b/app/javascript/src/components/Profile/Organization/Holidays/Details/index.tsx @@ -4,8 +4,8 @@ import dayjs from "dayjs"; import { Tooltip } from "StyledComponents"; import HolidayModal from "common/HolidayModal"; +import DetailsHeader from "components/Profile/Common/DetailsHeader"; -import Header from "./Header"; import TableHeader from "./TableHeader"; import TableRow from "./TableRow"; @@ -52,10 +52,15 @@ const Details = ({ return ( -
    diff --git a/app/javascript/src/components/Profile/Organization/Holidays/EditHolidays/Header.tsx b/app/javascript/src/components/Profile/Organization/Holidays/EditHolidays/Header.tsx deleted file mode 100644 index 3ac346025a..0000000000 --- a/app/javascript/src/components/Profile/Organization/Holidays/EditHolidays/Header.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from "react"; - -import CustomYearPicker from "common/CustomYearPicker"; - -const Header = ({ - cancelAction, - saveAction, - currentYear, - setCurrentYear, - isDisableUpdateBtn, -}: Iprops) => ( -
    - Holidays - -
    -
    - - -
    -
    -
    -); - -interface Iprops { - cancelAction?: () => any; - saveAction?: () => any; - isDisableUpdateBtn?: boolean; - currentYear: any; - setCurrentYear: () => any; -} - -export default Header; diff --git a/app/javascript/src/components/Profile/Organization/Holidays/EditHolidays/index.tsx b/app/javascript/src/components/Profile/Organization/Holidays/EditHolidays/index.tsx index f5dfa91966..3916f7c33c 100644 --- a/app/javascript/src/components/Profile/Organization/Holidays/EditHolidays/index.tsx +++ b/app/javascript/src/components/Profile/Organization/Holidays/EditHolidays/index.tsx @@ -9,9 +9,9 @@ import CustomReactSelect from "common/CustomReactSelect"; import CustomToggle from "common/CustomToggle"; import SingleYearDatePicker from "common/CustomYearPicker/SingleYearDatePicker"; import { Divider } from "common/Divider"; +import EditHeader from "components/Profile/Common/EditHeader"; import { allocationFrequency } from "constants/leaveType"; -import Header from "./Header"; import { customStyles } from "./utils"; const EditHolidays = ({ @@ -43,12 +43,16 @@ const EditHolidays = ({ updateHolidayDetails, }) => ( <> -
    diff --git a/app/javascript/src/components/Profile/Organization/Import/index.tsx b/app/javascript/src/components/Profile/Organization/Import/index.tsx index 5e380e3bcc..f71ecae6c1 100644 --- a/app/javascript/src/components/Profile/Organization/Import/index.tsx +++ b/app/javascript/src/components/Profile/Organization/Import/index.tsx @@ -6,7 +6,7 @@ import Loader from "common/Loader/index"; import ImportCard from "./importCard"; import ImportModal from "./importModal"; -import Header from "../../Header"; +import EditHeader from "../../Common/EditHeader"; const importList = [ { @@ -77,7 +77,7 @@ const Import = () => { return (
    -
    ( -
    - - - - - - -); - -export default CustomTableHeader; diff --git a/app/javascript/src/components/Profile/Organization/Leaves/Details/CustomTableRow.tsx b/app/javascript/src/components/Profile/Organization/Leaves/Details/CustomTableRow.tsx deleted file mode 100644 index dd5ed3232a..0000000000 --- a/app/javascript/src/components/Profile/Organization/Leaves/Details/CustomTableRow.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { Fragment } from "react"; - -const CustomTableRow = ({ leave, key }) => { - const { - customLeaveType, - customLeaveTotal, - customAllocationPeriod, - employees, - } = leave; - - return ( - - - - - - ); -}; - -export default CustomTableRow; diff --git a/app/javascript/src/components/Profile/Organization/Leaves/Details/Header.tsx b/app/javascript/src/components/Profile/Organization/Leaves/Details/Header.tsx deleted file mode 100644 index 1450acb53b..0000000000 --- a/app/javascript/src/components/Profile/Organization/Leaves/Details/Header.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from "react"; - -import CustomYearPicker from "common/CustomYearPicker"; - -const Header = ({ - editAction, - showYearPicker = false, - currentYear, - setCurrentYear, -}: Iprops) => ( -
    - Leaves - {showYearPicker && ( - - )} -
    -
    - -
    -
    -
    -); - -interface Iprops { - editAction?: () => any; - showYearPicker?: boolean; - currentYear: any; - setCurrentYear: () => any; -} - -export default Header; diff --git a/app/javascript/src/components/Profile/Organization/Leaves/Details/index.tsx b/app/javascript/src/components/Profile/Organization/Leaves/Details/index.tsx index fa847be6d1..a980b21a2b 100644 --- a/app/javascript/src/components/Profile/Organization/Leaves/Details/index.tsx +++ b/app/javascript/src/components/Profile/Organization/Leaves/Details/index.tsx @@ -1,25 +1,27 @@ import React from "react"; -import CustomTableHeader from "./CustomTableHeader"; -import CustomTableRow from "./CustomTableRow"; -import Header from "./Header"; +import DetailsHeader from "components/Profile/Common/DetailsHeader"; + import TableHeader from "./TableHeader"; import TableRow from "./TableRow"; const Details = ({ leavesList, - customLeavesList, showYearPicker, editAction, currentYear, setCurrentYear, }) => ( <> -
    @@ -39,23 +41,6 @@ const Details = ({
    - LEAVE TYPE - - TOTAL - - Employees -
    - - {customLeaveType} - - - {customLeaveTotal} {customAllocationPeriod} - - {employees.map((emp, index) => ( - - {emp.label}{" "} - {index < employees.length - 1 && ,} - - ))} -
    -
    -
    - Customised Leaves -
    - - - - {customLeavesList.length > 0 ? ( - customLeavesList.map((leave, index) => ( - - )) - ) : ( -
    No data found
    - )} -
    -
    -
    ); diff --git a/app/javascript/src/components/Profile/Organization/Leaves/EditLeaves/Header.tsx b/app/javascript/src/components/Profile/Organization/Leaves/EditLeaves/Header.tsx deleted file mode 100644 index 12454cef3d..0000000000 --- a/app/javascript/src/components/Profile/Organization/Leaves/EditLeaves/Header.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from "react"; - -import CustomYearPicker from "common/CustomYearPicker"; - -const Header = ({ - cancelAction, - saveAction, - showYearPicker = false, - currentYear, - setCurrentYear, - isDisableUpdateBtn, -}: Iprops) => ( -
    - Leaves - {showYearPicker && ( - - )} -
    -
    - - -
    -
    -
    -); - -interface Iprops { - cancelAction?: () => any; - saveAction?: () => any; - isDisableUpdateBtn?: boolean; - showYearPicker?: boolean; - currentYear: any; - setCurrentYear: () => any; -} - -export default Header; diff --git a/app/javascript/src/components/Profile/Organization/Leaves/EditLeaves/index.tsx b/app/javascript/src/components/Profile/Organization/Leaves/EditLeaves/index.tsx index 2af69d3b89..db8a7b4776 100644 --- a/app/javascript/src/components/Profile/Organization/Leaves/EditLeaves/index.tsx +++ b/app/javascript/src/components/Profile/Organization/Leaves/EditLeaves/index.tsx @@ -6,9 +6,9 @@ import { Button } from "StyledComponents"; import { CustomInputText } from "common/CustomInputText"; import CustomReactSelect from "common/CustomReactSelect"; import { Divider } from "common/Divider"; +import EditHeader from "components/Profile/Common/EditHeader"; import { allocationFrequency, getAllocationPeriod } from "constants/leaveType"; -import Header from "./Header"; import { ColorOption, iconColorStyles, @@ -18,10 +18,8 @@ import { const EditLeaves = ({ leaveBalanceList, - customLeavesList, - handleOnChangeLeaves, - handleOnChangeCustomLeaves, - handleDeleteLeave, + updateCondition, + handleDeleteLeaveBalance, handleAddLeaveType, handleLeaveTypeChange, iconOptions, @@ -33,8 +31,6 @@ const EditLeaves = ({ currentYear, setCurrentYear, isDisableUpdateBtn, - handleAddCustomLeave, - employees, }) => { const getAllocationPeriodValue = (allocationFrequency, allocationPeriod) => { const availableOptions = getAllocationPeriod(allocationFrequency); @@ -47,13 +43,16 @@ const EditLeaves = ({ return ( <> -
    @@ -90,7 +89,7 @@ const EditLeaves = ({
    {e.icon}
    )} handleOnChange={e => - handleOnChangeLeaves("leaveIcon", e, index) + updateCondition("leaveIcon", e, index) } /> )} handleOnChange={e => - handleOnChangeLeaves("leaveColor", e, index) + updateCondition("leaveColor", e, index) } />
    @@ -129,7 +128,7 @@ const EditLeaves = ({ value={leaveBalance.total || ""} wrapperClassName="w-full lg:w-2/12 lg:mr-2 mb-6 lg:mb-0" onChange={e => - handleOnChangeLeaves("total", e.target.value, index) + updateCondition("total", e.target.value, index) } /> null, }} handleOnChange={e => - handleOnChangeLeaves("allocationPeriod", e.value, index) + updateCondition("allocationPeriod", e.value, index) } options={getAllocationPeriod( leaveBalance.allocationFrequency @@ -163,13 +162,8 @@ const EditLeaves = ({ IndicatorSeparator: () => null, }} handleOnChange={e => { - handleOnChangeLeaves( - "allocationFrequency", - e.value, - index - ); - - handleOnChangeLeaves( + updateCondition("allocationFrequency", e.value, index); + updateCondition( "allocationPeriod", getAllocationPeriodValue( leaveBalance.allocationFrequency, @@ -199,7 +193,7 @@ const EditLeaves = ({ value={leaveBalance.carryForwardDays || ""} wrapperClassName="w-full lg:w-4/12 mb-6 lg:mb-0" onChange={e => - handleOnChangeLeaves( + updateCondition( "carryForwardDays", e.target.value, index @@ -207,13 +201,15 @@ const EditLeaves = ({ } />
    - + +
    {leaveBalanceList.length - 1 != index && ( @@ -223,124 +219,17 @@ const EditLeaves = ({ )}
    ))} - -
    -
    -
    - -
    -
    Customised Leaves
    -
    - {customLeavesList.map((customLeave, index) => ( -
    -
    - handleLeaveTypeChange(e, index, true)} - /> - - handleOnChangeCustomLeaves( - "customLeaveTotal", - e.target.value, - index - ) - } - /> - null, - }} - handleOnChange={e => - handleOnChangeCustomLeaves( - "customAllocationPeriod", - e, - index - ) - } - value={getAllocationPeriodValue( - "", - customLeave.allocationPeriod - )} - /> -
    -
    -
    - null, - }} - handleOnChange={e => { - handleOnChangeCustomLeaves("employees", e, index); - }} - /> -
    - -
    -
    - {leaveBalanceList.length - 1 != index && ( -
    - -
    - )}
    - ))} - +
    -
    - {!isDesktop && - (leaveBalanceList.length > 0 || customLeavesList.legth > 0) && ( + {!isDesktop && leaveBalanceList.length > 0 && (
    )} +
    ); diff --git a/app/javascript/src/components/Profile/Organization/Leaves/EditLeaves/utils.js b/app/javascript/src/components/Profile/Organization/Leaves/EditLeaves/utils.js index 2f4dd3b07d..99672ab2b0 100644 --- a/app/javascript/src/components/Profile/Organization/Leaves/EditLeaves/utils.js +++ b/app/javascript/src/components/Profile/Organization/Leaves/EditLeaves/utils.js @@ -74,14 +74,6 @@ export const customStyles = { letterSpacing: "2px", zIndex: 5, }), - valueContainer: provided => ({ - ...provided, - overflow: "visible", - }), - multiValue: provided => ({ - ...provided, - fontSize: "14px", - }), placeholder: base => ({ ...base, position: "absolute", diff --git a/app/javascript/src/components/Profile/Organization/Leaves/index.tsx b/app/javascript/src/components/Profile/Organization/Leaves/index.tsx index 4072d7983f..f8efdfa10f 100644 --- a/app/javascript/src/components/Profile/Organization/Leaves/index.tsx +++ b/app/javascript/src/components/Profile/Organization/Leaves/index.tsx @@ -5,7 +5,6 @@ import { getYear } from "date-fns"; import { useNavigate } from "react-router-dom"; import leavesApi from "apis/leaves"; -import teamApi from "apis/team"; import Loader from "common/Loader/index"; import { leaveIcons, leaveColors } from "constants/leaveType"; import { useUserContext } from "context/UserContext"; @@ -13,26 +12,18 @@ import { sendGAPageView } from "utils/googleAnalytics"; import Details from "./Details"; import EditLeaves from "./EditLeaves"; -import { - makeLeavePayload, - makeLeavesList, - makeCustomLeavesList, - makeCustomLeavePayload, -} from "./utils"; +import { makeLeavePayload, makeLeavesList } from "./utils"; const Leaves = () => { const [leaveBalanceList, setLeaveBalanceList] = useState([]); - const [leaves, setLeaves] = useState([]); - const [customLeavesList, setCustomLeavesList] = useState([]); const [iconOptions, setIconOptions] = useState(leaveIcons); const [colorOptions, setColorOptions] = useState(leaveColors); const [isLoading, setIsLoading] = useState(true); const [currentYear, setCurrentYear] = useState(getYear(new Date())); + const [leaves, setLeaves] = useState([]); const [currentYearLeaves, setCurrentYearLeaves] = useState([]); - const [currentYearCustomLeaves, setCurrentYearCustomLeaves] = useState([]); const [isEditable, setIsEditable] = useState(false); const [isDisableUpdateBtn, setIsDisableUpdateBtn] = useState(false); - const [employees, setEmployees] = useState>([]); const { isDesktop } = useUserContext(); const navigate = useNavigate(); @@ -41,7 +32,6 @@ const Leaves = () => { useEffect(() => { fetchLeaves(); - fetchEmployees(); }, []); useEffect(() => { @@ -58,19 +48,6 @@ const Leaves = () => { setIsLoading(false); }; - const fetchEmployees = async () => { - const res = await teamApi.get(); - const empData = res.data.combinedDetails; - const empList = empData - .filter(emp => emp.isTeamMember) - .map(emp => ({ - value: emp.id, - label: emp.name, - })); - - setEmployees(empList); - }; - useEffect(() => { if (leaves.length) { updateLeaveBalanceList(); @@ -79,23 +56,12 @@ const Leaves = () => { const updateLeaveBalanceList = (allLeaves = leaves) => { const currentLeaves = allLeaves.find(leave => leave.year == currentYear); - if (currentLeaves) { - if (currentLeaves?.leave_types.length) { - setLeaveBalanceList(makeLeavesList(currentLeaves?.leave_types)); - setCurrentYearLeaves(makeLeavesList(currentLeaves?.leave_types)); - } - - if (currentLeaves?.custom_leaves.length) { - setCustomLeavesList(makeCustomLeavesList(currentLeaves?.custom_leaves)); - setCurrentYearCustomLeaves( - makeCustomLeavesList(currentLeaves?.custom_leaves) - ); - } + if (currentLeaves?.leave_types.length) { + setLeaveBalanceList(makeLeavesList(currentLeaves?.leave_types)); + setCurrentYearLeaves(makeLeavesList(currentLeaves?.leave_types)); } else { setLeaveBalanceList([]); - setCustomLeavesList([]); setCurrentYearLeaves([]); - setCurrentYearCustomLeaves([]); } }; @@ -116,32 +82,12 @@ const Leaves = () => { ]); }; - const handleAddCustomLeave = () => { - setCustomLeavesList([ - ...customLeavesList, - ...[ - { - customLeaveType: "", - customLeaveTotal: 0, - customAllocationPeriod: "days", - employees: [], - }, - ], - ]); - }; - - const handleOnChangeLeaves = (type, value, index) => { + const updateCondition = (type, value, index) => { const editLeaveList = [...leaveBalanceList]; editLeaveList[index][type] = value; setLeaveBalanceList([...editLeaveList]); }; - const handleOnChangeCustomLeaves = (type, value, index) => { - const editCustomLeaveList = [...customLeavesList]; - editCustomLeaveList[index][type] = value; - setCustomLeavesList([...editCustomLeaveList]); - }; - const handleUpdateDetails = async () => { const payload = { add_leave_types: [], @@ -149,15 +95,8 @@ const Leaves = () => { removed_leave_type_ids: [], }; - const customPayload = { - add_custom_leaves: [], - update_custom_leaves: [], - remove_custom_leaves: [], - }; - setIsDisableUpdateBtn(true); - //filtering out removed leaves const removedLeaves = currentYearLeaves .filter( currentLeave => @@ -165,17 +104,7 @@ const Leaves = () => { ) .map(removedLeave => removedLeave.id); - //filtering out removed custom leaves - const removedCustomLeaves = currentYearCustomLeaves - .filter( - currentLeave => - !customLeavesList.some(leave => leave.id === currentLeave.id) - ) - .map(removedLeave => removedLeave.id); - - //updating payload payload.removed_leave_type_ids.push(...removedLeaves); - customPayload.remove_custom_leaves.push(...removedCustomLeaves); const leavesList = leaveBalanceList.filter( leave => @@ -187,7 +116,6 @@ const Leaves = () => { }) ); - //updating leaves payload for add and update leavesList.forEach(leave => { if (leave.id) { payload.updated_leave_types.push(makeLeavePayload(leave)); @@ -196,38 +124,12 @@ const Leaves = () => { } }); - const customLeaves = customLeavesList.filter( - customLeave => - !currentYearCustomLeaves.some(currentLeave => { - const leaveJSON = JSON.stringify(customLeave); - const currentLeaveJSON = JSON.stringify(currentLeave); - - return leaveJSON === currentLeaveJSON; - }) - ); - - //updating custom leaves payload for add and update - customLeaves.forEach(customLeave => { - if (customLeave.id) { - customPayload.update_custom_leaves.push( - makeCustomLeavePayload(customLeave) - ); - } else { - customPayload.add_custom_leaves.push( - makeCustomLeavePayload(customLeave) - ); - } - }); - - updateLeaveDetails(payload, customPayload); + updateLeaveDetails(payload); }; - const updateLeaveDetails = async (payload, customPayload) => { + const updateLeaveDetails = async payload => { try { await leavesApi.updateLeaveWithLeaveTypes(currentYear, payload); - await leavesApi.customLeaves(currentYear, { - custom_leaves: customPayload, - }); setIsDisableUpdateBtn(false); setIsEditable(false); fetchLeaves(); @@ -246,21 +148,13 @@ const Leaves = () => { } }; - const handleDeleteLeave = (leave, isCustom = false) => { - if (isCustom) { - setCustomLeavesList(customLeavesList.filter(prev => prev !== leave)); - } else { - setLeaveBalanceList(leaveBalanceList.filter(prev => prev !== leave)); - } + const handleDeleteLeaveBalance = leave => { + setLeaveBalanceList(leaveBalanceList.filter(prev => prev !== leave)); }; - const handleLeaveTypeChange = (e, index, isCustom = false) => { + const handleLeaveTypeChange = (e, index) => { const result = e.target.value.replace(/[^a-zA-Z- ]/g, ""); - if (isCustom) { - handleOnChangeCustomLeaves("customLeaveType", result, index); - } else { - handleOnChangeLeaves("leaveType", result, index); - } + updateCondition("leaveType", result, index); }; const handleIconSelect = () => { @@ -294,27 +188,22 @@ const Leaves = () => { showYearPicker colorOptions={colorOptions} currentYear={currentYear} - customLeavesList={customLeavesList} - employees={employees} - handleAddCustomLeave={handleAddCustomLeave} handleAddLeaveType={handleAddLeaveType} handleCancelAction={handleCancelAction} - handleDeleteLeave={handleDeleteLeave} + handleDeleteLeaveBalance={handleDeleteLeaveBalance} handleLeaveTypeChange={handleLeaveTypeChange} - handleOnChangeCustomLeaves={handleOnChangeCustomLeaves} - handleOnChangeLeaves={handleOnChangeLeaves} iconOptions={iconOptions} isDesktop={isDesktop} isDisableUpdateBtn={isDisableUpdateBtn} leaveBalanceList={leaveBalanceList} setCurrentYear={setCurrentYear} + updateCondition={updateCondition} updateLeaveDetails={handleUpdateDetails} /> ) : (
    setIsEditable(true)} leavesList={currentYearLeaves} setCurrentYear={setCurrentYear} diff --git a/app/javascript/src/components/Profile/Organization/Leaves/utils.ts b/app/javascript/src/components/Profile/Organization/Leaves/utils.ts index 8a2fde8bbe..3b3c0ac0f6 100644 --- a/app/javascript/src/components/Profile/Organization/Leaves/utils.ts +++ b/app/javascript/src/components/Profile/Organization/Leaves/utils.ts @@ -39,14 +39,6 @@ export const makeLeavePayload = leave => ({ carry_forward_days: leave.carryForwardDays, }); -export const makeCustomLeavePayload = leave => ({ - id: leave.id, - name: leave.customLeaveType, - allocation_value: leave.customLeaveTotal, - allocation_period: leave.customAllocationPeriod, - user_ids: leave.employees.map(emp => emp.value), -}); - export const makeLeavesList = leaveTypes => leaveTypes.map(leaveType => ({ id: leaveType.id, @@ -58,15 +50,3 @@ export const makeLeavesList = leaveTypes => allocationFrequency: leaveType.allocation_frequency, carryForwardDays: leaveType.carry_forward_days, })); - -export const makeCustomLeavesList = customLeaves => - customLeaves.map(leave => ({ - id: leave.id, - customLeaveType: leave.name, - customLeaveTotal: leave.allocation_value, - customAllocationPeriod: leave.allocation_period, - employees: leave.users.map(emp => ({ - value: emp.id, - label: emp.full_name, - })), - })); diff --git a/app/javascript/src/components/Profile/Organization/Payment/StaticPage.tsx b/app/javascript/src/components/Profile/Organization/Payment/StaticPage.tsx index 207515576b..57105d7ec2 100644 --- a/app/javascript/src/components/Profile/Organization/Payment/StaticPage.tsx +++ b/app/javascript/src/components/Profile/Organization/Payment/StaticPage.tsx @@ -5,7 +5,7 @@ import { ConnectSVG, StripeLogoSVG, disconnectAccountSVG } from "miruIcons"; import Loader from "common/Loader/index"; import { ApiStatus as PaymentSettingsStatus } from "constants/index"; -import Header from "../../Header"; +import EditHeader from "../../Common/EditHeader"; const StaticPage = ({ isStripeConnected, @@ -14,7 +14,7 @@ const StaticPage = ({ status, }) => ( <> -
    { const initialErrState = { @@ -20,8 +21,12 @@ const CompensationEditPage = () => { deduction_amount_err: "", }; const navigate = useNavigate(); + const { memberId } = useParams(); const { isDesktop, company } = useUserContext(); - + const { isCalledFromSettings } = useProfileContext(); + const navigateToPath = isCalledFromSettings + ? "/settings" + : `/team/${memberId}`; const [isLoading, setIsLoading] = useState(false); const [earnings, setEarnings] = useState>( CompensationDetailsState.earnings @@ -99,7 +104,7 @@ const CompensationEditPage = () => { const handleCancelDetails = () => { setIsLoading(true); - navigate(`/settings/compensation`, { replace: true }); + navigate(`${navigateToPath}/compensation`, { replace: true }); }; return ( diff --git a/app/javascript/src/components/Profile/UserDetail/CompensationDetails/StaticPage.tsx b/app/javascript/src/components/Profile/Personal/Compensation/StaticPage.tsx similarity index 100% rename from app/javascript/src/components/Profile/UserDetail/CompensationDetails/StaticPage.tsx rename to app/javascript/src/components/Profile/Personal/Compensation/StaticPage.tsx diff --git a/app/javascript/src/components/Profile/UserDetail/CompensationDetails/index.tsx b/app/javascript/src/components/Profile/Personal/Compensation/index.tsx similarity index 71% rename from app/javascript/src/components/Profile/UserDetail/CompensationDetails/index.tsx rename to app/javascript/src/components/Profile/Personal/Compensation/index.tsx index b7e3eb9ae0..e8aa230db3 100644 --- a/app/javascript/src/components/Profile/UserDetail/CompensationDetails/index.tsx +++ b/app/javascript/src/components/Profile/Personal/Compensation/index.tsx @@ -1,19 +1,22 @@ import React, { Fragment, useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import Loader from "common/Loader/index"; import { MobileEditHeader } from "common/Mobile/MobileEditHeader"; -import { useProfile } from "components/Profile/context/EntryContext"; -import DetailsHeader from "components/Profile/DetailsHeader"; +import { useProfileContext } from "context/Profile/ProfileContext"; import { useUserContext } from "context/UserContext"; import StaticPage from "./StaticPage"; +import DetailsHeader from "../../Common/DetailsHeader"; + const CompensationDetails = () => { const { isDesktop, company } = useUserContext(); - const { setUserState, compensationDetails } = useProfile(); + const { updateDetails, compensationDetails, isCalledFromSettings } = + useProfileContext(); const navigate = useNavigate(); + const { memberId } = useParams(); const [isLoading, setIsLoading] = useState(false); const getDetails = async () => { @@ -29,10 +32,14 @@ const CompensationDetails = () => { amount: "147500", }, }; - setUserState("compensationDetails", compensationData); + updateDetails("compensationDetails", compensationData); setIsLoading(false); }; + const handleEditClick = () => { + navigate(`edit`, { replace: true }); + }; + useEffect(() => { setIsLoading(true); getDetails(); @@ -43,17 +50,15 @@ const CompensationDetails = () => { {isDesktop ? ( - navigate(`/settings/compensation/edit`, { replace: true }) - } /> ) : ( )} diff --git a/app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/Device.ts b/app/javascript/src/components/Profile/Personal/Devices/Device.ts similarity index 100% rename from app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/Device.ts rename to app/javascript/src/components/Profile/Personal/Devices/Device.ts diff --git a/app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/Edit/EditPage.tsx b/app/javascript/src/components/Profile/Personal/Devices/Edit/EditPage.tsx similarity index 100% rename from app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/Edit/EditPage.tsx rename to app/javascript/src/components/Profile/Personal/Devices/Edit/EditPage.tsx diff --git a/app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/Edit/MobileEditPage.tsx b/app/javascript/src/components/Profile/Personal/Devices/Edit/MobileEditPage.tsx similarity index 100% rename from app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/Edit/MobileEditPage.tsx rename to app/javascript/src/components/Profile/Personal/Devices/Edit/MobileEditPage.tsx diff --git a/app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/Edit/index.tsx b/app/javascript/src/components/Profile/Personal/Devices/Edit/index.tsx similarity index 70% rename from app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/Edit/index.tsx rename to app/javascript/src/components/Profile/Personal/Devices/Edit/index.tsx index fe54e59fed..fb3b638544 100644 --- a/app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/Edit/index.tsx +++ b/app/javascript/src/components/Profile/Personal/Devices/Edit/index.tsx @@ -1,11 +1,13 @@ /* eslint-disable no-unused-vars */ import React, { Fragment, useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import deviceApi from "apis/devices"; import Loader from "common/Loader/index"; import { MobileDetailsHeader } from "common/Mobile/MobileDetailsHeader"; +import EditHeader from "components/Profile/Common/EditHeader"; +import { useProfileContext } from "context/Profile/ProfileContext"; import { useUserContext } from "context/UserContext"; import EditPage from "./EditPage"; @@ -26,7 +28,13 @@ const AllocatedDevicesEdit = () => { }; const navigate = useNavigate(); const { isDesktop, user } = useUserContext(); + const { memberId } = useParams(); + const { isCalledFromSettings } = useProfileContext(); + const navigateToPath = isCalledFromSettings + ? "/settings" + : `/team/${memberId}`; + const currentUserId = isCalledFromSettings ? user.id : memberId; const [isLoading, setIsLoading] = useState(false); const [devices, setDevices] = useState([]); const [errDetails, setErrDetails] = useState(initialErrState); @@ -37,15 +45,19 @@ const AllocatedDevicesEdit = () => { }, []); const getDevicesDetail = async () => { - const res: any = await deviceApi.get(user.id); + const res: any = await deviceApi.get(currentUserId); const devicesDetails: Device[] = res.data.devices; setDevices(devicesDetails); setIsLoading(false); }; + const handleUpdateDetails = () => { + //Todo: API integration for update details + }; + const handleCancelDetails = () => { setIsLoading(true); - navigate(`/settings/devices`, { replace: true }); + navigate(`${navigateToPath}/devices`, { replace: true }); }; const addAnotherDevice = () => { @@ -68,20 +80,14 @@ const AllocatedDevicesEdit = () => { {isDesktop && ( -
    -

    Allocated Devices

    -
    - - -
    -
    + {isLoading ? ( ) : ( @@ -92,7 +98,7 @@ const AllocatedDevicesEdit = () => { {!isDesktop && ( {isLoading ? ( diff --git a/app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/StaticPage.tsx b/app/javascript/src/components/Profile/Personal/Devices/StaticPage.tsx similarity index 100% rename from app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/StaticPage.tsx rename to app/javascript/src/components/Profile/Personal/Devices/StaticPage.tsx diff --git a/app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/helpers.ts b/app/javascript/src/components/Profile/Personal/Devices/helpers.ts similarity index 100% rename from app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/helpers.ts rename to app/javascript/src/components/Profile/Personal/Devices/helpers.ts diff --git a/app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/index.tsx b/app/javascript/src/components/Profile/Personal/Devices/index.tsx similarity index 72% rename from app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/index.tsx rename to app/javascript/src/components/Profile/Personal/Devices/index.tsx index ab614a9b8b..5f06c02ea8 100644 --- a/app/javascript/src/components/Profile/UserDetail/AllocatedDevicesDetails/index.tsx +++ b/app/javascript/src/components/Profile/Personal/Devices/index.tsx @@ -1,11 +1,12 @@ import React, { Fragment, useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import deviceApi from "apis/devices"; import Loader from "common/Loader/index"; import { MobileEditHeader } from "common/Mobile/MobileEditHeader"; -import DetailsHeader from "components/Profile/DetailsHeader"; +import DetailsHeader from "components/Profile/Common/DetailsHeader"; +import { useProfileContext } from "context/Profile/ProfileContext"; import { useUserContext } from "context/UserContext"; import { Device } from "./Device"; @@ -13,13 +14,16 @@ import StaticPage from "./StaticPage"; const AllocatedDevicesDetails = () => { const { user, isDesktop } = useUserContext(); + const { isCalledFromSettings } = useProfileContext(); const navigate = useNavigate(); + const { memberId } = useParams(); + const currentUserId = isCalledFromSettings ? user.id : memberId; const [isLoading, setIsLoading] = useState(false); const [devices, setDevices] = useState([]); const getDevicesDetail = async () => { - const res: any = await deviceApi.get(user.id); + const res: any = await deviceApi.get(currentUserId); const devicesDetails: Device[] = res.data.devices; setDevices(devicesDetails); setIsLoading(false); @@ -31,7 +35,7 @@ const AllocatedDevicesDetails = () => { }, []); const handleEdit = () => { - navigate(`/settings/devices/edit`, { replace: true }); + navigate(`edit`, { replace: true }); }; return ( @@ -46,8 +50,8 @@ const AllocatedDevicesDetails = () => { /> ) : ( )} diff --git a/app/javascript/src/components/Profile/UserDetail/EmploymentDetails/Edit/MobileEditPage.tsx b/app/javascript/src/components/Profile/Personal/Employment/Edit/MobileEditPage.tsx similarity index 100% rename from app/javascript/src/components/Profile/UserDetail/EmploymentDetails/Edit/MobileEditPage.tsx rename to app/javascript/src/components/Profile/Personal/Employment/Edit/MobileEditPage.tsx diff --git a/app/javascript/src/components/Profile/UserDetail/EmploymentDetails/Edit/StaticPage.tsx b/app/javascript/src/components/Profile/Personal/Employment/Edit/StaticPage.tsx similarity index 100% rename from app/javascript/src/components/Profile/UserDetail/EmploymentDetails/Edit/StaticPage.tsx rename to app/javascript/src/components/Profile/Personal/Employment/Edit/StaticPage.tsx diff --git a/app/javascript/src/components/Profile/UserDetail/EmploymentDetails/Edit/index.tsx b/app/javascript/src/components/Profile/Personal/Employment/Edit/index.tsx similarity index 86% rename from app/javascript/src/components/Profile/UserDetail/EmploymentDetails/Edit/index.tsx rename to app/javascript/src/components/Profile/Personal/Employment/Edit/index.tsx index 6c3748b63f..3681a59f8f 100644 --- a/app/javascript/src/components/Profile/UserDetail/EmploymentDetails/Edit/index.tsx +++ b/app/javascript/src/components/Profile/Personal/Employment/Edit/index.tsx @@ -4,14 +4,15 @@ import React, { Fragment, useEffect, useRef, useState } from "react"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; import { useOutsideClick } from "helpers"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import * as Yup from "yup"; import teamsApi from "apis/teams"; import Loader from "common/Loader/index"; import { MobileDetailsHeader } from "common/Mobile/MobileDetailsHeader"; -import { useProfile } from "components/Profile/context/EntryContext"; -import { employmentSchema } from "components/Team/Details/EmploymentDetails/Edit/validationSchema"; +import EditHeader from "components/Profile/Common/EditHeader"; +import { employmentSchema } from "components/Profile/Schema/employmentSchema"; +import { useProfileContext } from "context/Profile/ProfileContext"; import { useUserContext } from "context/UserContext"; import { employmentMapper } from "mapper/teams.mapper"; @@ -36,8 +37,10 @@ const EmploymentDetailsEdit = () => { role_err: "", }; const { user } = useUserContext(); - const { setUserState, employmentDetails } = useProfile(); + const { updateDetails, employmentDetails, isCalledFromSettings } = + useProfileContext(); const navigate = useNavigate(); + const { memberId } = useParams(); const { isDesktop } = useUserContext(); const DOJRef = useRef(null); @@ -64,12 +67,18 @@ const EmploymentDetailsEdit = () => { const [resignedAt, setResignedAt] = useState(null); const [joinedAt, setJoinedAt] = useState(null); + const currentUserId = isCalledFromSettings ? user.id : memberId; + + const navigateToPath = isCalledFromSettings + ? "/settings" + : `/team/${memberId}`; + useOutsideClick(DOJRef, () => setShowDOJDatePicker({ visibility: false })); useOutsideClick(DORRef, () => setShowDORDatePicker({ visibility: false })); const getDetails = async () => { - const curr: any = await teamsApi.getEmploymentDetails(user.id); - const prev: any = await teamsApi.getPreviousEmployments(user.id); + const curr: any = await teamsApi.getEmploymentDetails(currentUserId); + const prev: any = await teamsApi.getPreviousEmployments(currentUserId); setDateFormat(curr.data.date_format); setJoinedAt(curr.data.employment.joined_at); setResignedAt(curr.data.employment.resigned_at); @@ -89,7 +98,7 @@ const EmploymentDetailsEdit = () => { employmentData.current_employment.employment_type = employeeTypes[0].value; } - setUserState("employmentDetails", employmentData); + updateDetails("employmentDetails", employmentData); if (employmentData.previous_employments?.length > 0) { setPreviousEmployments(employmentData.previous_employments); } @@ -103,7 +112,7 @@ const EmploymentDetailsEdit = () => { const handleOnChangeEmployeeType = empType => { setEmployeeType(empType); - setUserState("employmentDetails", { + updateDetails("employmentDetails", { ...employmentDetails, ...{ current_employment: { @@ -115,7 +124,7 @@ const EmploymentDetailsEdit = () => { }; const updateCurrentEmploymentDetails = (value, type) => { - setUserState("employmentDetails", { + updateDetails("employmentDetails", { ...employmentDetails, ...{ current_employment: { @@ -139,7 +148,7 @@ const EmploymentDetailsEdit = () => { const handleDOJDatePicker = date => { setShowDOJDatePicker({ visibility: !showDOJDatePicker.visibility }); setJoinedAt(date); - setUserState("employmentDetails", { + updateDetails("employmentDetails", { ...employmentDetails, ...{ current_employment: { @@ -155,7 +164,7 @@ const EmploymentDetailsEdit = () => { const handleDORDatePicker = date => { setShowDORDatePicker({ visibility: !showDORDatePicker.visibility }); setResignedAt(date); - setUserState("employmentDetails", { + updateDetails("employmentDetails", { ...employmentDetails, ...{ current_employment: { @@ -246,11 +255,11 @@ const EmploymentDetailsEdit = () => { }, }; - await teamsApi.updatePreviousEmployments(user.id, { + await teamsApi.updatePreviousEmployments(currentUserId, { employments: payload, }); setIsLoading(false); - navigate(`/settings/employment`, { replace: true }); + navigate(`${navigateToPath}/employment`, { replace: true }); } catch (err) { setIsLoading(false); const errObj = initialErrState; @@ -269,32 +278,21 @@ const EmploymentDetailsEdit = () => { const handleCancelDetails = () => { setIsLoading(true); - navigate(`/settings/employment`, { replace: true }); + navigate(`${navigateToPath}/employment`, { replace: true }); }; return ( {isDesktop && ( -
    -

    - Employment Details -

    -
    - - -
    -
    + {isLoading ? ( ) : ( @@ -327,7 +325,7 @@ const EmploymentDetailsEdit = () => { {!isDesktop && ( {isLoading ? ( diff --git a/app/javascript/src/components/Profile/UserDetail/EmploymentDetails/StaticPage.tsx b/app/javascript/src/components/Profile/Personal/Employment/StaticPage.tsx similarity index 100% rename from app/javascript/src/components/Profile/UserDetail/EmploymentDetails/StaticPage.tsx rename to app/javascript/src/components/Profile/Personal/Employment/StaticPage.tsx diff --git a/app/javascript/src/components/Profile/UserDetail/EmploymentDetails/helpers.ts b/app/javascript/src/components/Profile/Personal/Employment/helpers.ts similarity index 100% rename from app/javascript/src/components/Profile/UserDetail/EmploymentDetails/helpers.ts rename to app/javascript/src/components/Profile/Personal/Employment/helpers.ts diff --git a/app/javascript/src/components/Profile/UserDetail/EmploymentDetails/index.tsx b/app/javascript/src/components/Profile/Personal/Employment/index.tsx similarity index 61% rename from app/javascript/src/components/Profile/UserDetail/EmploymentDetails/index.tsx rename to app/javascript/src/components/Profile/Personal/Employment/index.tsx index 233734e00b..493bf51585 100644 --- a/app/javascript/src/components/Profile/UserDetail/EmploymentDetails/index.tsx +++ b/app/javascript/src/components/Profile/Personal/Employment/index.tsx @@ -1,12 +1,12 @@ import React, { Fragment, useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import teamsApi from "apis/teams"; import Loader from "common/Loader/index"; import { MobileEditHeader } from "common/Mobile/MobileEditHeader"; -import { useProfile } from "components/Profile/context/EntryContext"; -import DetailsHeader from "components/Profile/DetailsHeader"; +import DetailsHeader from "components/Profile/Common/DetailsHeader"; +import { useProfileContext } from "context/Profile/ProfileContext"; import { useUserContext } from "context/UserContext"; import { employmentMapper } from "mapper/teams.mapper"; @@ -14,21 +14,29 @@ import StaticPage from "./StaticPage"; const EmploymentDetails = () => { const { user, isDesktop } = useUserContext(); - const { setUserState, employmentDetails } = useProfile(); + const { updateDetails, employmentDetails, isCalledFromSettings } = + useProfileContext(); const navigate = useNavigate(); + const { memberId } = useParams(); + const [isLoading, setIsLoading] = useState(false); + const currentUserId = isCalledFromSettings ? user.id : memberId; const getDetails = async () => { - const res1: any = await teamsApi.getEmploymentDetails(user.id); - const res: any = await teamsApi.getPreviousEmployments(user.id); + const res1: any = await teamsApi.getEmploymentDetails(currentUserId); + const res: any = await teamsApi.getPreviousEmployments(currentUserId); const employmentData = employmentMapper( res1.data.employment, res.data.previous_employments ); - setUserState("employmentDetails", employmentData); + updateDetails("employmentDetails", employmentData); setIsLoading(false); }; + const handleEditClick = () => { + navigate(`edit`, { replace: true }); + }; + useEffect(() => { setIsLoading(true); getDetails(); @@ -39,18 +47,16 @@ const EmploymentDetails = () => { {isDesktop && ( - navigate(`/settings/employment/edit`, { replace: true }) - } /> )} {!isDesktop && ( )} diff --git a/app/javascript/src/components/Profile/UserDetail/Edit/MobileEditPage.tsx b/app/javascript/src/components/Profile/Personal/User/Edit/MobileEditPage.tsx similarity index 100% rename from app/javascript/src/components/Profile/UserDetail/Edit/MobileEditPage.tsx rename to app/javascript/src/components/Profile/Personal/User/Edit/MobileEditPage.tsx diff --git a/app/javascript/src/components/Profile/UserDetail/Edit/StaticPage.tsx b/app/javascript/src/components/Profile/Personal/User/Edit/StaticPage.tsx similarity index 100% rename from app/javascript/src/components/Profile/UserDetail/Edit/StaticPage.tsx rename to app/javascript/src/components/Profile/Personal/User/Edit/StaticPage.tsx diff --git a/app/javascript/src/components/Profile/UserDetail/Edit/index.tsx b/app/javascript/src/components/Profile/Personal/User/Edit/index.tsx similarity index 71% rename from app/javascript/src/components/Profile/UserDetail/Edit/index.tsx rename to app/javascript/src/components/Profile/Personal/User/Edit/index.tsx index 6544492b01..48279ffae6 100644 --- a/app/javascript/src/components/Profile/UserDetail/Edit/index.tsx +++ b/app/javascript/src/components/Profile/Personal/User/Edit/index.tsx @@ -9,9 +9,11 @@ import { useNavigate } from "react-router-dom"; import * as Yup from "yup"; import profileApi from "apis/profile"; +import teamsApi from "apis/teams"; import Loader from "common/Loader/index"; import { MobileDetailsHeader } from "common/Mobile/MobileDetailsHeader"; -import { useProfile } from "components/Profile/context/EntryContext"; +import EditHeader from "components/Profile/Common/EditHeader"; +import { useProfileContext } from "context/Profile/ProfileContext"; import { useUserContext } from "context/UserContext"; import { teamsMapper } from "mapper/teams.mapper"; @@ -42,6 +44,10 @@ const UserDetailsEdit = () => { const navigate = useNavigate(); const { isDesktop } = useUserContext(); + const { + personalDetails: { id }, + isCalledFromSettings, + } = useProfileContext(); const wrapperRef = useRef(null); @@ -52,7 +58,7 @@ const UserDetailsEdit = () => { const [isLoading, setIsLoading] = useState(false); const [addrId, setAddrId] = useState(); const [userId, setUserId] = useState(); - const { setUserState, profileSettings } = useProfile(); + const { updateDetails, personalDetails } = useProfileContext(); const [changePassword, setChangePassword] = useState(false); const [showCurrentPassword, setShowCurrentPassword] = useState(false); @@ -63,6 +69,8 @@ const UserDetailsEdit = () => { const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); + const navigateToPath = isCalledFromSettings ? "/settings" : `/team/${id}`; + useOutsideClick(wrapperRef, () => setShowDatePicker({ visibility: false })); const assignCountries = async allCountries => { const countryData = await allCountries.map(country => ({ @@ -74,12 +82,12 @@ const UserDetailsEdit = () => { }; const getDetails = async () => { - const data = await profileApi.index(); - const addressData = await profileApi.getAddress(data.data.user.id); - setUserId(data.data.user.id); + const data = await teamsApi.get(id); + const addressData = await teamsApi.getAddress(id); + setUserId(data.data.id); - const userObj = teamsMapper(data.data.user, addressData.data.addresses[0]); - setUserState("profileSettings", userObj); + const userObj = teamsMapper(data.data, addressData.data.addresses[0]); + updateDetails("personalDetails", userObj); if (userObj.addresses?.address_type?.length > 0) { setAddrType( addressOptions.find( @@ -96,22 +104,22 @@ const UserDetailsEdit = () => { const allCountries = Country.getAllCountries(); assignCountries(allCountries); getDetails(); - }, []); + }, [id]); const cancelPasswordChange = () => { setChangePassword(false); - setUserState("profileSettings", { - ...profileSettings, + updateDetails("personalDetails", { + ...personalDetails, ...{ confirmPassword: "", password: "", currentPassword: "" }, }); }; const handleOnChangeCountry = selectCountry => { - setUserState("profileSettings", { - ...profileSettings, + updateDetails("personalDetails", { + ...personalDetails, ...{ addresses: { - ...profileSettings.addresses, + ...personalDetails.addresses, ...{ country: selectCountry.value, state: "", city: "" }, }, }, @@ -120,11 +128,11 @@ const UserDetailsEdit = () => { const handleOnChangeAddrType = addreType => { setAddrType(addreType); - setUserState("profileSettings", { - ...profileSettings, + updateDetails("personalDetails", { + ...personalDetails, ...{ addresses: { - ...profileSettings.addresses, + ...personalDetails.addresses, ...{ address_type: addreType.value }, }, }, @@ -133,15 +141,15 @@ const UserDetailsEdit = () => { const updateBasicDetails = (value, type, isAddress = false) => { if (isAddress) { - setUserState("profileSettings", { - ...profileSettings, + updateDetails("personalDetails", { + ...personalDetails, ...{ - addresses: { ...profileSettings.addresses, ...{ [type]: value } }, + addresses: { ...personalDetails.addresses, ...{ [type]: value } }, }, }); } else { - setUserState("profileSettings", { - ...profileSettings, + updateDetails("personalDetails", { + ...personalDetails, ...{ [type]: value }, }); } @@ -149,12 +157,12 @@ const UserDetailsEdit = () => { const handleDatePicker = date => { setShowDatePicker({ visibility: !showDatePicker.visibility }); - const formattedDate = dayjs(date, profileSettings.date_format).format( - profileSettings.date_format + const formattedDate = dayjs(date, personalDetails.date_format).format( + personalDetails.date_format ); - setUserState("profileSettings", { - ...profileSettings, + updateDetails("personalDetails", { + ...personalDetails, ...{ date_of_birth: formattedDate }, }); }; @@ -163,10 +171,10 @@ const UserDetailsEdit = () => { try { await schema.validate( { - ...profileSettings, + ...personalDetails, ...{ - is_email: profileSettings.email_id - ? profileSettings.email_id.length > 0 + is_email: personalDetails.email_id + ? personalDetails.email_id.length > 0 : false, changePassword, }, @@ -175,54 +183,60 @@ const UserDetailsEdit = () => { ); const userSchema = { - first_name: profileSettings.first_name, - last_name: profileSettings.last_name, - date_of_birth: profileSettings.date_of_birth + first_name: personalDetails.first_name, + last_name: personalDetails.last_name, + date_of_birth: personalDetails.date_of_birth ? dayjs - .utc(profileSettings.date_of_birth, profileSettings.date_format) + .utc(personalDetails.date_of_birth, personalDetails.date_format) .toISOString() : null, - phone: profileSettings.phone_number - ? profileSettings.phone_number + phone: personalDetails.phone_number + ? personalDetails.phone_number : null, - personal_email_id: profileSettings.email_id, + personal_email_id: personalDetails.email_id, social_accounts: { - linkedin_url: profileSettings.linkedin, - github_url: profileSettings.github, + linkedin_url: personalDetails.linkedin, + github_url: personalDetails.github, }, }; if (changePassword) { - userSchema["current_password"] = profileSettings.currentPassword; - userSchema["password"] = profileSettings.password; - userSchema["password_confirmation"] = profileSettings.confirmPassword; + userSchema["current_password"] = personalDetails.currentPassword; + userSchema["password"] = personalDetails.password; + userSchema["password_confirmation"] = personalDetails.confirmPassword; } const payload = { address: { - address_line_1: profileSettings.addresses.address_line_1, - address_line_2: profileSettings.addresses.address_line_2, - address_type: profileSettings.addresses.address_type, - city: profileSettings.addresses.city, - state: profileSettings.addresses.state, - country: profileSettings.addresses.country, - pin: profileSettings.addresses.pin, + address_line_1: personalDetails.addresses.address_line_1, + address_line_2: personalDetails.addresses.address_line_2, + address_type: personalDetails.addresses.address_type, + city: personalDetails.addresses.city, + state: personalDetails.addresses.state, + country: personalDetails.addresses.country, + pin: personalDetails.addresses.pin, }, }; - await profileApi.update({ - user: userSchema, - }); + if (isCalledFromSettings) { + await profileApi.update({ + user: userSchema, + }); + } else { + await teamsApi.updateUser(id, { + user: userSchema, + }); + } if (addrId) { - await profileApi.updateAddress(userId, addrId, { - address: { ...profileSettings.addresses }, + await teamsApi.updateAddress(userId, addrId, { + address: { ...personalDetails.addresses }, }); } else { - await profileApi.createAddress(userId, payload); + await teamsApi.createAddress(userId, payload); } setErrDetails(initialErrState); - navigate(`/settings/profile`, { replace: true }); + navigate(`${navigateToPath}/profile`, { replace: true }); } catch (err) { setIsLoading(false); const errObj = initialErrState; @@ -245,7 +259,7 @@ const UserDetailsEdit = () => { const handleCancelDetails = () => { setIsLoading(true); - navigate(`/settings/profile`, { replace: true }); + navigate(`${navigateToPath}/profile`, { replace: true }); }; const handleCurrentPasswordChange = event => { @@ -266,23 +280,14 @@ const UserDetailsEdit = () => { {isDesktop && ( -
    -

    Personal Details

    -
    - - -
    -
    + {isLoading ? ( ) : ( @@ -294,7 +299,7 @@ const UserDetailsEdit = () => { confirmPassword={confirmPassword} countries={countries} currentPassword={currentPassword} - dateFormat={profileSettings.date_format} + dateFormat={personalDetails.date_format} errDetails={errDetails} getErr={getErr} handleConfirmPasswordChange={handleConfirmPasswordChange} @@ -305,7 +310,7 @@ const UserDetailsEdit = () => { handlePasswordChange={handlePasswordChange} handlePhoneNumberChange={handlePhoneNumberChange} password={password} - personalDetails={profileSettings} + personalDetails={personalDetails} setChangePassword={setChangePassword} setErrDetails={setErrDetails} setShowConfirmPassword={setShowConfirmPassword} @@ -325,7 +330,7 @@ const UserDetailsEdit = () => { {!isDesktop && ( {isLoading ? ( @@ -338,7 +343,7 @@ const UserDetailsEdit = () => { changePassword={changePassword} countries={countries} currentPassword={currentPassword} - dateFormat={profileSettings.date_format} + dateFormat={personalDetails.date_format} errDetails={errDetails} handleCancelDetails={handleCancelDetails} handleCurrentPasswordChange={handleCurrentPasswordChange} @@ -347,7 +352,7 @@ const UserDetailsEdit = () => { handleOnChangeCountry={handleOnChangeCountry} handlePhoneNumberChange={handlePhoneNumberChange} handleUpdateDetails={handleUpdateDetails} - personalDetails={profileSettings} + personalDetails={personalDetails} setChangePassword={setChangePassword} setErrDetails={setErrDetails} setShowConfirmPassword={setShowConfirmPassword} diff --git a/app/javascript/src/components/Profile/UserDetail/Edit/validationSchema.ts b/app/javascript/src/components/Profile/Personal/User/Edit/validationSchema.ts similarity index 100% rename from app/javascript/src/components/Profile/UserDetail/Edit/validationSchema.ts rename to app/javascript/src/components/Profile/Personal/User/Edit/validationSchema.ts diff --git a/app/javascript/src/components/Profile/UserDetail/UserDetailsView/MobilePersonalDetails.tsx b/app/javascript/src/components/Profile/Personal/User/MobilePersonalDetails.tsx similarity index 85% rename from app/javascript/src/components/Profile/UserDetail/UserDetailsView/MobilePersonalDetails.tsx rename to app/javascript/src/components/Profile/Personal/User/MobilePersonalDetails.tsx index 05f970c74b..bcdad03f85 100644 --- a/app/javascript/src/components/Profile/UserDetail/UserDetailsView/MobilePersonalDetails.tsx +++ b/app/javascript/src/components/Profile/Personal/User/MobilePersonalDetails.tsx @@ -19,6 +19,7 @@ const MobilePersonalDetails = ({ date_format, }, handleEditClick, + isCalledFromSettings, }) => { const { address_line_1 = "", @@ -116,20 +117,22 @@ const MobilePersonalDetails = ({ -
    - - - Password - -
    - + {isCalledFromSettings && ( +
    + + + Password + +
    + +
    -
    + )}
    ); }; diff --git a/app/javascript/src/components/Profile/UserDetail/UserDetailsView/StaticPage.tsx b/app/javascript/src/components/Profile/Personal/User/StaticPage.tsx similarity index 83% rename from app/javascript/src/components/Profile/UserDetail/UserDetailsView/StaticPage.tsx rename to app/javascript/src/components/Profile/Personal/User/StaticPage.tsx index 3cb9058be5..3d63e829f9 100644 --- a/app/javascript/src/components/Profile/UserDetail/UserDetailsView/StaticPage.tsx +++ b/app/javascript/src/components/Profile/Personal/User/StaticPage.tsx @@ -5,7 +5,11 @@ import customParseFormat from "dayjs/plugin/customParseFormat"; import { InfoIcon, KeyIcon, PhoneIcon, MapPinIcon, GlobeIcon } from "miruIcons"; dayjs.extend(customParseFormat); -const StaticPage = ({ personalDetails, handleEditClick }) => ( +const StaticPage = ({ + personalDetails, + handleEditClick, + isCalledFromSettings, +}) => (
    @@ -112,26 +116,28 @@ const StaticPage = ({ personalDetails, handleEditClick }) => (
    -
    -
    -
    - - - Password - -
    -
    -
    - + {isCalledFromSettings && ( +
    +
    +
    + + + Password + +
    +
    +
    + +
    -
    + )}
    ); diff --git a/app/javascript/src/components/Profile/UserDetail/UserDetailsView/index.tsx b/app/javascript/src/components/Profile/Personal/User/index.tsx similarity index 64% rename from app/javascript/src/components/Profile/UserDetail/UserDetailsView/index.tsx rename to app/javascript/src/components/Profile/Personal/User/index.tsx index add5b182fe..ef1f91a768 100644 --- a/app/javascript/src/components/Profile/UserDetail/UserDetailsView/index.tsx +++ b/app/javascript/src/components/Profile/Personal/User/index.tsx @@ -1,15 +1,12 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ - import React, { Fragment, useEffect, useState } from "react"; -import { Outlet, useNavigate } from "react-router-dom"; +import { Outlet, useNavigate, useParams } from "react-router-dom"; -import profileApi from "apis/profile"; import teamsApi from "apis/teams"; import Loader from "common/Loader/index"; import { MobileEditHeader } from "common/Mobile/MobileEditHeader"; -import { useProfile } from "components/Profile/context/EntryContext"; -import DetailsHeader from "components/Profile/DetailsHeader"; +import DetailsHeader from "components/Profile/Common/DetailsHeader"; +import { useProfileContext } from "context/Profile/ProfileContext"; import { useUserContext } from "context/UserContext"; import { employmentMapper, teamsMapper } from "mapper/teams.mapper"; import { sendGAPageView } from "utils/googleAnalytics"; @@ -18,32 +15,35 @@ import MobilePersonalDetails from "./MobilePersonalDetails"; import StaticPage from "./StaticPage"; const UserDetailsView = () => { - const { setUserState, profileSettings } = useProfile(); + const { updateDetails, personalDetails, isCalledFromSettings } = + useProfileContext(); const [isLoading, setIsLoading] = useState(false); - const { isDesktop, companyRole } = useUserContext(); + const { user, isDesktop, companyRole } = useUserContext(); + const { memberId } = useParams(); + const UserId = window.location.pathname.startsWith("/settings") + ? user.id + : memberId; const getData = async () => { setIsLoading(true); - const res = await profileApi.index(); + const res = await teamsApi.get(UserId); if (res.status && res.status == 200) { - const addressData = await profileApi.getAddress(res.data.user.id); - const userObj = teamsMapper(res.data.user, addressData.data.addresses[0]); + const addressData = await teamsApi.getAddress(UserId); + const userObj = teamsMapper(res.data, addressData.data.addresses[0]); - setUserState("profileSettings", userObj); + updateDetails("personalDetails", userObj); if (companyRole !== "client") { - const employmentData: any = await teamsApi.getEmploymentDetails( - res?.data?.user.id - ); + const employmentData: any = await teamsApi.getEmploymentDetails(UserId); const previousEmploymentData: any = - await teamsApi.getPreviousEmployments(res?.data?.user.id); + await teamsApi.getPreviousEmployments(UserId); if (employmentData.status && employmentData.status == 200) { const employmentObj = employmentMapper( employmentData.data.employment, previousEmploymentData.data.previous_employments ); - setUserState("employmentDetails", employmentObj); + updateDetails("employmentDetails", employmentObj); } } setIsLoading(false); @@ -60,7 +60,7 @@ const UserDetailsView = () => { const navigate = useNavigate(); const handleEditClick = () => { - navigate(`/settings/profile/edit`, { replace: true }); + navigate(`edit`, { replace: true }); }; return ( @@ -79,7 +79,8 @@ const UserDetailsView = () => { ) : ( )} @@ -87,16 +88,19 @@ const UserDetailsView = () => { {!isDesktop && ( {isLoading ? ( ) : ( )} diff --git a/app/javascript/src/components/Profile/RouteConfig.tsx b/app/javascript/src/components/Profile/RouteConfig.tsx deleted file mode 100644 index 1982b5f5ec..0000000000 --- a/app/javascript/src/components/Profile/RouteConfig.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, { useEffect } from "react"; - -import { Routes, Route, Navigate } from "react-router-dom"; - -import profileApi from "apis/profile"; -import ErrorPage from "common/Error"; -import { useUserContext } from "context/UserContext"; -import { teamsMapper } from "mapper/teams.mapper"; - -import { useProfile } from "./context/EntryContext"; -import { SETTINGS_ROUTES } from "./routes"; - -const ProtectedRoute = ({ role, authorisedRoles, children }) => { - if (authorisedRoles.includes(role)) { - return children; - } - - return ; -}; - -const RouteConfig = () => { - const { companyRole } = useUserContext(); - const { - setUserState, - profileSettings: { first_name, last_name }, - } = useProfile(); - - const getData = async () => { - if (!first_name && !last_name) { - const res = await profileApi.index(); - if (res.status && res.status == 200) { - const addressData = await profileApi.getAddress(res.data.user.id); - const userObj = teamsMapper( - res.data.user, - addressData.data.addresses[0] - ); - setUserState("profileSettings", userObj); - } - } - }; - - useEffect(() => { - getData(); - }, []); - - return ( - - {SETTINGS_ROUTES.map(({ path, authorisedRoles, Component }) => ( - - - - } - /> - ))} - } path="*" /> - - ); -}; - -export default RouteConfig; diff --git a/app/javascript/src/components/Team/Details/EmploymentDetails/Edit/validationSchema.ts b/app/javascript/src/components/Profile/Schema/employmentSchema.ts similarity index 100% rename from app/javascript/src/components/Team/Details/EmploymentDetails/Edit/validationSchema.ts rename to app/javascript/src/components/Profile/Schema/employmentSchema.ts diff --git a/app/javascript/src/components/Profile/SubNav.tsx b/app/javascript/src/components/Profile/SubNav.tsx deleted file mode 100644 index 76150998b1..0000000000 --- a/app/javascript/src/components/Profile/SubNav.tsx +++ /dev/null @@ -1,152 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -import React, { useState } from "react"; - -import { MinusIcon, PlusIcon } from "miruIcons"; -import { NavLink } from "react-router-dom"; - -import { useUserContext } from "context/UserContext"; - -import UserInformation from "./CommonComponents/UserInformation"; -import { companySettingsList, personalSettingsList } from "./constants"; - -const SubNav = ({ isAdmin, firstName, company, lastName, designation }) => { - const { companyRole } = useUserContext(); - - const getActiveClassName = isActive => { - if (isActive) { - return "pl-4 py-5 border-l-8 border-miru-han-purple-600 bg-miru-gray-200 text-miru-han-purple-600 block w-full flex items-center"; - } - - return "pl-6 py-5 border-b-1 border-miru-gray-400 block w-full flex items-center"; - }; - - const [openedSubNav, setOpenedSubNav] = useState({ - personal: true, - company: false, - }); - - const getAdminLinks = () => ( -
      -
      - setOpenedSubNav({ - ...openedSubNav, - personal: !openedSubNav.personal, - }) - } - > - Personal - -
      - {openedSubNav.personal && ( -
        - {personalSettingsList.map((setting, index) => { - if (setting.authorisedRoles.includes(companyRole)) { - return ( -
      • - -
      • - ); - } - })} -
      - )} -
      - setOpenedSubNav({ ...openedSubNav, company: !openedSubNav.company }) - } - > - {company.name} - -
      - {openedSubNav.company && ( -
        - {companySettingsList.map((setting, index) => { - if (setting.authorisedRoles.includes(companyRole)) { - return ( -
      • - -
      • - ); - } - })} -
      - )} -
    - ); - - const getEmployeeLinks = () => ( -
      - {personalSettingsList.map((setting, index) => { - if (setting.authorisedRoles.includes(companyRole)) { - return ( -
    • - -
    • - ); - } - })} -
    - ); - - const SideBarNavItem = ({ label, link, icon }) => ( - getActiveClassName(isActive)} - to={link} - > - {icon} - {label} - - ); - - return ( -
    - -
    - {isAdmin ? getAdminLinks() : getEmployeeLinks()} -
    -
    - ); -}; - -export default SubNav; diff --git a/app/javascript/src/components/Profile/UserDetail/index.tsx b/app/javascript/src/components/Profile/UserDetail/index.tsx deleted file mode 100644 index 12aff71ab5..0000000000 --- a/app/javascript/src/components/Profile/UserDetail/index.tsx +++ /dev/null @@ -1,456 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ - -import React, { useEffect, useState } from "react"; - -import { - DeleteIconSVG, - EditImageButtonSVG, - PasswordIconSVG, - PasswordIconTextSVG, - PlusIconSVG, -} from "miruIcons"; -import { Toastr } from "StyledComponents"; -import * as Yup from "yup"; - -import profileApi from "apis/profile"; -import { Divider } from "common/Divider"; -import Loader from "common/Loader/index"; -import { sendGAPageView } from "utils/googleAnalytics"; - -import { useProfile } from "../context/EntryContext"; -import Header from "../Header"; - -const userProfileSchema = Yup.object().shape({ - firstName: Yup.string().required("First Name cannot be blank"), - lastName: Yup.string().required("Last Name cannot be blank"), - changePassword: Yup.boolean(), - password: Yup.string().when("changePassword", { - is: true, - then: Yup.string().required("Please enter password"), - }), - currentPassword: Yup.string().when("changePassword", { - is: true, - then: Yup.string().required("Please enter current password"), - }), - - confirmPassword: Yup.string().when("changePassword", { - is: true, - then: Yup.string().oneOf( - [Yup.ref("password"), null], - "Passwords don't match" - ), - }), -}); - -const UserDetails = () => { - const initialErrState = { - firstNameErr: "", - lastNameErr: "", - passwordErr: "", - currentPasswordErr: "", - confirmPasswordErr: "", - }; - - const { setUserState } = useProfile(); - const [profileImage, setProfileImage] = useState(""); - const [imageFile, setImageFile] = useState(null); - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - const [email, setEmail] = useState(""); - const [currentPassword, setCurrentPassword] = useState(""); - const [password, setPassword] = useState(""); - const [confirmPassword, setConfirmPassword] = useState(""); - const [changePassword, setChangePassword] = useState(false); - const [showPassword, setShowPassword] = useState(false); - const [showCurrentPassword, setShowCurrentPassword] = - useState(false); - - const [showConfirmPassword, setShowConfirmPassword] = - useState(false); - const [isDetailUpdated, setIsDetailUpdated] = useState(false); - const [errDetails, setErrDetails] = useState(initialErrState); - const [isLoading, setIsLoading] = useState(false); - - const handleProfileImageChange = e => { - const imageFile = e.target.files[0]; - setProfileImage(URL.createObjectURL(imageFile)); - setImageFile(imageFile); - setIsDetailUpdated(true); - }; - - const handleUpdateProfile = async () => { - try { - await userProfileSchema.validate( - { - firstName, - lastName, - changePassword, - password, - confirmPassword, - currentPassword, - }, - { abortEarly: false } - ); - await updateProfile(); - } catch (err) { - setIsLoading(false); - const errObj = initialErrState; - err.inner.map(item => { - errObj[`${item.path}Err`] = item.message; - }); - setErrDetails(errObj); - } - }; - - const updateProfile = async () => { - try { - setIsLoading(true); - const formD = new FormData(); - formD.append("user[first_name]", firstName); - formD.append("user[last_name]", lastName); - if (changePassword) { - formD.append("user[current_password]", currentPassword); - formD.append("user[password]", password); - formD.append("user[password_confirmation]", confirmPassword); - } - - if (imageFile) { - formD.append("user[avatar]", imageFile); - } - await profileApi.update(formD); - setIsDetailUpdated(false); - setErrDetails(initialErrState); - setUserState("profileSettings", { - firstName, - lastName, - }); - setIsLoading(false); - } catch { - setIsLoading(false); - Toastr.error("Error in Updating user Details"); - } - }; - - const handleFirstNameChange = event => { - setFirstName(event.target.value); - setIsDetailUpdated(true); - setErrDetails({ ...errDetails, firstNameErr: "" }); - }; - - const handleLastNameChange = event => { - setLastName(event.target.value); - setIsDetailUpdated(true); - setErrDetails({ ...errDetails, lastNameErr: "" }); - }; - - const handleCurrentPasswordChange = event => { - setCurrentPassword(event.target.value); - setIsDetailUpdated(true); - setErrDetails({ ...errDetails, currentPasswordErr: "" }); - }; - - const handlePasswordChange = event => { - setPassword(event.target.value); - setIsDetailUpdated(true); - setErrDetails({ ...errDetails, passwordErr: "" }); - }; - - const handleConfirmPasswordChange = event => { - setConfirmPassword(event.target.value); - setIsDetailUpdated(true); - }; - - const getData = async () => { - setIsLoading(true); - const data = await profileApi.index(); - if (data.status && data.status == 200) { - setFirstName(data.data.user.first_name); - setLastName(data.data.user.last_name); - setProfileImage(data.data.user.avatar_url); - setEmail(data.data.user.email); - setUserState("profileSettings", { - firstName: data.data.user.first_name, - lastName: data.data.user.last_name, - email: data.data.user.email, - }); - setIsLoading(false); - } else { - setFirstName(""); - setLastName(""); - setProfileImage(""); - setEmail(""); - setIsLoading(false); - } - }; - - useEffect(() => { - sendGAPageView(); - getData(); - }, []); - - const handleCancelAction = () => { - getData(); - setIsDetailUpdated(false); - setErrDetails(initialErrState); - setChangePassword(false); - setCurrentPassword(""); - setPassword(""); - setConfirmPassword(""); - }; - - const handleDeleteLogo = async () => { - const removeProfile = await profileApi.removeAvatar(); - if (removeProfile.status === 200) { - setImageFile(null); - setProfileImage(""); - } - }; - - const getErr = errMsg =>

    {errMsg}

    ; - - return ( -
    -
    - {isLoading ? ( -
    - -
    - ) : ( -
    -
    -
    Basic Details
    -
    - Profile Picture - {profileImage ? ( -
    -
    - profile_pic -
    - - - -
    - ) : ( - <> -
    - -
    - - - )} -
    - -
    -
    - - {errDetails.firstNameErr && getErr(errDetails.firstNameErr)} -
    -
    - - {errDetails.lastNameErr && getErr(errDetails.lastNameErr)} -
    -
    -
    -
    - - setEmail(event.target.value)} - /> -
    -
    -
    - -
    -
    Password
    -
    -
    - {!changePassword && ( -
    -

    setChangePassword(true)} - > - CHANGE PASSWORD -

    -
    - )} - {changePassword && ( -
    -
    -
    - -
    - - -
    - {errDetails.currentPasswordErr && - getErr(errDetails.currentPasswordErr)} -
    -
    -
    - -
    - - -
    - {errDetails.passwordErr && - getErr(errDetails.passwordErr)} -
    -
    - -
    - - -
    - {errDetails.confirmPasswordErr && - getErr(errDetails.confirmPasswordErr)} -
    -
    -

    { - setChangePassword(false); - setErrDetails(initialErrState); - }} - > - CANCEL -

    -
    -
    - )} -
    -
    -
    -
    - )} -
    - ); -}; - -export default UserDetails; diff --git a/app/javascript/src/components/Profile/constants.js b/app/javascript/src/components/Profile/constants.js deleted file mode 100644 index 317fb0f3d7..0000000000 --- a/app/javascript/src/components/Profile/constants.js +++ /dev/null @@ -1,76 +0,0 @@ -import React from "react"; - -import { - ClientsIcon as BuildingsIcon, - CalendarIcon, - CakeIcon, - PaymentsIcon, - UserIcon, - MobileIcon, - ProjectsIcon, -} from "miruIcons"; - -import { Roles } from "constants/index"; - -const { ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE, CLIENT } = Roles; - -export const personalSettingsList = [ - { - label: "PROFILE SETTINGS", - link: "/settings/profile", - icon: , - authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE, CLIENT], - }, - { - label: "EMPLOYMENT DETAILS", - link: "/settings/employment", - icon: , - authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE], - }, - { - label: "ALLOCATED DEVICES", - link: "/settings/devices", - icon: , - authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE], - }, - //TODO: Uncomment when Integrating with API - // { - // label: "COMPENSATION", - // link: "/settings/compensation", - // icon: , - // authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE], - // }, -]; - -export const companySettingsList = [ - { - label: "ORG. SETTINGS", - link: "/settings/organization", - icon: , - authorisedRoles: [ADMIN, OWNER], - }, - { - label: "PAYMENT SETTINGS", - link: "/settings/payment", - icon: , - authorisedRoles: [ADMIN, OWNER], - }, - { - label: "LEAVES", - link: "/settings/leaves", - icon: , - authorisedRoles: [ADMIN, OWNER], - }, - { - label: "HOLIDAYS", - link: "/settings/holidays", - icon: , - authorisedRoles: [ADMIN, OWNER], - }, - // { - // label: "Integration", - // link: "/settings/integrations", - // icon: , - // authorisedRoles: [ADMIN, OWNER], - // }, -]; diff --git a/app/javascript/src/components/Profile/context/EntryContext.tsx b/app/javascript/src/components/Profile/context/EntryContext.tsx deleted file mode 100644 index 8fa61bc78b..0000000000 --- a/app/javascript/src/components/Profile/context/EntryContext.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { createContext, useContext } from "react"; - -import { CompensationDetailsState } from "./CompensationDetailsState"; -import { EmploymentDetailsState } from "./EmploymentDetailsState"; -import { PersonalDetailsState } from "./PersonalDetailsState"; - -const EntryContext = createContext({ - profileSettings: PersonalDetailsState, - employmentDetails: EmploymentDetailsState, - compensationDetails: CompensationDetailsState, - organizationSettings: {}, - bankAccDetails: {}, - paymentSettings: {}, - billing: {}, - setUserState: (key, value) => {}, //eslint-disable-line -}); - -export const useProfile = () => useContext(EntryContext); - -export default EntryContext; diff --git a/app/javascript/src/components/Profile/index.tsx b/app/javascript/src/components/Profile/index.tsx new file mode 100644 index 0000000000..3ad5076614 --- /dev/null +++ b/app/javascript/src/components/Profile/index.tsx @@ -0,0 +1,98 @@ +import React, { Fragment, useState, useEffect } from "react"; + +import { useLocation, useParams } from "react-router-dom"; + +import { ProfileContext } from "context/Profile/ProfileContext"; +import { useUserContext } from "context/UserContext"; + +import { CompensationDetailsState } from "./Context/CompensationDetailsState"; +import { EmploymentDetailsState } from "./Context/EmploymentDetailsState"; +import { PersonalDetailsState } from "./Context/PersonalDetailsState"; +import Header from "./Layout/Header"; +import SideNav from "./Layout/Navigation"; +import MobileNav from "./Layout/Navigation/MobileNav"; +import OutletWrapper from "./Layout/OutletWrapper"; + +const Layout = () => { + const { isDesktop } = useUserContext(); + const location = useLocation(); + const { memberId } = useParams(); + const [settingsStates, setSettingsStates] = useState({ + personalDetails: PersonalDetailsState, + employmentDetails: EmploymentDetailsState, + documentDetails: {}, + deviceDetails: {}, + compensationDetails: CompensationDetailsState, + reimburstmentDetails: {}, + }); + + const [isCalledFromSettings, setIsCalledFromSettings] = useState(false); + const [isCalledFromTeam, setIsCalledFromTeam] = useState(false); + const [showMobileNav, setShowMobileNav] = useState(false); + + useEffect(() => { + if (location.pathname.startsWith("/settings")) { + setIsCalledFromSettings(true); + } else { + setIsCalledFromSettings(false); + } + + if (location.pathname.startsWith("/team")) { + setIsCalledFromTeam(true); + } else { + setIsCalledFromTeam(false); + } + + const mobileNavVisibility = + location.pathname === "/settings" || + location.pathname === "/settings/" || + location.pathname === `/team/${memberId}` || + location.pathname === `/team/${memberId}/`; + + setShowMobileNav(mobileNavVisibility); + }, [location]); + + const updateDetails = (key, value) => { + setSettingsStates(previousSettings => ({ + ...previousSettings, + ...{ [key]: { ...previousSettings[key], ...value } }, + })); + }; + + return ( + + {isDesktop && ( + +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    + )} + {!isDesktop && ( + + {showMobileNav && } + + + )} +
    + ); +}; + +export default Layout; diff --git a/app/javascript/src/components/Profile/routes.ts b/app/javascript/src/components/Profile/routes.ts deleted file mode 100644 index 7ff1b29e98..0000000000 --- a/app/javascript/src/components/Profile/routes.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Roles } from "constants/index"; - -import MobileNav from "./Layout/MobileNav"; -import OrgDetails from "./Organization/Details"; -import OrgEdit from "./Organization/Edit"; -import Holidays from "./Organization/Holidays"; -import Leaves from "./Organization/Leaves"; -import PaymentSettings from "./Organization/Payment"; -import AllocatedDevicesDetails from "./UserDetail/AllocatedDevicesDetails"; -import AllocatedDevicesEdit from "./UserDetail/AllocatedDevicesDetails/Edit"; -import CompensationDetails from "./UserDetail/CompensationDetails"; -import CompensationDetailsEdit from "./UserDetail/CompensationDetails/Edit"; -import UserDetailsEdit from "./UserDetail/Edit"; -import EmploymentDetails from "./UserDetail/EmploymentDetails"; -import EmploymentDetailsEdit from "./UserDetail/EmploymentDetails/Edit"; -import UserDetailsView from "./UserDetail/UserDetailsView"; - -const { ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE, CLIENT } = Roles; - -export const SETTINGS_ROUTES = [ - { - path: "/profile", - Component: UserDetailsView, - authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE, CLIENT], - }, - { - path: "/profile/edit", - Component: UserDetailsEdit, - authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE, CLIENT], - }, - { - path: "/employment", - Component: EmploymentDetails, - authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE], - }, - { - path: "/employment/edit", - Component: EmploymentDetailsEdit, - authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE], - }, - { - path: "/devices", - Component: AllocatedDevicesDetails, - authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE], - }, - { - path: "/devices/edit", - Component: AllocatedDevicesEdit, - authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE], - }, - { - path: "/compensation", - Component: CompensationDetails, - authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE], - }, - { - path: "/compensation/edit", - Component: CompensationDetailsEdit, - authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE], - }, - { - path: "/", - Component: MobileNav, - authorisedRoles: [ADMIN, OWNER, BOOK_KEEPER, EMPLOYEE, CLIENT], - }, - { - path: "/organization", - Component: OrgDetails, - authorisedRoles: [ADMIN, OWNER], - }, - { - path: "/organization/edit", - Component: OrgEdit, - authorisedRoles: [ADMIN, OWNER], - }, - { - path: "/payment", - Component: PaymentSettings, - authorisedRoles: [ADMIN, OWNER], - }, - // { - // path: "/import", - // Component: OrganizationImport, - // authorisedRoles: [ADMIN, OWNER], - // }, - // { - // path: "/billing", - // Component: Billing, - // authorisedRoles: [ADMIN, OWNER], - // }, - { - path: "/leaves", - Component: Leaves, - authorisedRoles: [ADMIN, OWNER], - }, - { - path: "/holidays", - Component: Holidays, - authorisedRoles: [ADMIN, OWNER], - }, - // { - // path: "/integrations", - // Component: GoogleCalendar, - // authorisedRoles: [ADMIN, OWNER], - // }, -]; diff --git a/app/javascript/src/components/Team/Details/CompensationDetails/CompensationDetailsState.tsx b/app/javascript/src/components/Team/Details/CompensationDetails/CompensationDetailsState.tsx deleted file mode 100644 index f993aa7b2d..0000000000 --- a/app/javascript/src/components/Team/Details/CompensationDetails/CompensationDetailsState.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export const CompensationDetailsState = { - earnings: [], - deductions: [], - total: { - amount: 0, - }, -}; diff --git a/app/javascript/src/components/Team/Details/CompensationDetails/Edit/EditPage.tsx b/app/javascript/src/components/Team/Details/CompensationDetails/Edit/EditPage.tsx deleted file mode 100644 index 235addf5ad..0000000000 --- a/app/javascript/src/components/Team/Details/CompensationDetails/Edit/EditPage.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import React from "react"; - -import { currencyFormat } from "helpers"; -import { - DeleteIcon, - CoinsIcon, - EarningsIconSVG, - DeductionIconSVG, -} from "miruIcons"; -import "react-phone-number-input/style.css"; -import { Button } from "StyledComponents"; - -import { CustomInputText } from "common/CustomInputText"; - -const EditPage = ({ - handleAddEarning, - handleAddDeduction, - updateDeductionValues, - updateEarningsValues, - handleDeleteEarning, - handleDeleteDeduction, - earnings, - deductions, - total, - currency, -}) => ( -
    -
    -
    - - - Earnings - -
    -
    - {earnings.length > 0 && - earnings.map((earning, index) => ( -
    -
    -
    - { - updateEarningsValues(earning, e); - }} - /> -
    -
    - { - updateEarningsValues(earning, e); - }} - /> -
    -
    - -
    - ))} -
    - -
    -
    -
    -
    -
    -
    - - - Deductions - -
    -
    - {deductions.length > 0 && - deductions.map((deduction, index) => ( -
    -
    -
    - { - updateDeductionValues(deduction, e); - }} - /> -
    -
    - { - updateDeductionValues(deduction, e); - }} - /> -
    -
    - -
    - ))} -
    - -
    -
    -
    -
    -
    -
    - - - Total - -
    -
    - - {currencyFormat(currency, total)} - -
    -
    -
    -); - -export default EditPage; diff --git a/app/javascript/src/components/Team/Details/CompensationDetails/Edit/MobileEditPage.tsx b/app/javascript/src/components/Team/Details/CompensationDetails/Edit/MobileEditPage.tsx deleted file mode 100644 index e700055902..0000000000 --- a/app/javascript/src/components/Team/Details/CompensationDetails/Edit/MobileEditPage.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import React from "react"; - -import { currencyFormat } from "helpers"; -import { - CoinsIcon, - DeductionIconSVG, - DeleteIcon, - EarningsIconSVG, -} from "miruIcons"; -import "react-phone-number-input/style.css"; -import { Button } from "StyledComponents"; - -import { CustomInputText } from "common/CustomInputText"; - -const MobileEditPage = ({ - handleAddEarning, - handleAddDeduction, - updateDeductionValues, - updateEarningsValues, - handleDeleteEarning, - handleDeleteDeduction, - handleCancelDetails, - handleUpdateDetails, - earnings, - deductions, - total, - currency, -}) => ( -
    -
    -
    - - - Earnings - -
    -
    - {earnings.length > 0 ? ( - earnings.map((earning, index) => ( -
    -
    -
    - { - updateEarningsValues(earning, e); - }} - /> -
    - -
    -
    - { - updateEarningsValues(earning, e); - }} - /> -
    -
    - )) - ) : ( -
    No Earnings found
    - )} -
    - -
    -
    -
    -
    -
    - - - Deductions - -
    -
    - {deductions.length > 0 ? ( - deductions.map((deduction, index) => ( -
    -
    -
    - { - updateDeductionValues(deduction, e); - }} - /> -
    - -
    -
    - { - updateDeductionValues(deduction, e); - }} - /> -
    -
    - )) - ) : ( -
    No deductions found
    - )} -
    - -
    -
    -
    -
    -
    - - - Total - -
    -
    - - {currencyFormat(currency, total)} - -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -); - -export default MobileEditPage; diff --git a/app/javascript/src/components/Team/Details/CompensationDetails/Edit/index.tsx b/app/javascript/src/components/Team/Details/CompensationDetails/Edit/index.tsx deleted file mode 100644 index 214ef161e9..0000000000 --- a/app/javascript/src/components/Team/Details/CompensationDetails/Edit/index.tsx +++ /dev/null @@ -1,153 +0,0 @@ -/* eslint-disable no-unused-vars */ -import React, { Fragment, useEffect, useState } from "react"; - -import { useNavigate } from "react-router-dom"; - -import Loader from "common/Loader/index"; -import { CompensationDetailsState } from "components/Profile/context/CompensationDetailsState"; -import Header from "components/Profile/Header"; -import { useUserContext } from "context/UserContext"; - -import EditPage from "./EditPage"; -import MobileEditPage from "./MobileEditPage"; - -const CompensationEditPage = () => { - //TODO: add state for errDetails after API integration - // const initialErrState = { - // earning_type_err: "", - // earning_amount_err: "", - // deduction_type_err: "", - // deduction_amount_err: "", - // }; - const navigate = useNavigate(); - const { isDesktop, company } = useUserContext(); - - const [isLoading, setIsLoading] = useState(false); - const [earnings, setEarnings] = useState>( - CompensationDetailsState.earnings - ); - - const [deductions, setDeductions] = useState>( - CompensationDetailsState.deductions - ); - - const [total, setTotal] = useState( - CompensationDetailsState.total.amount - ); - - useEffect(() => { - setIsLoading(true); - getDevicesDetail(); - }, []); - - useEffect(() => { - const totalEarnings = earnings.reduce( - (accumulator, currentValue) => accumulator + currentValue["amount"], - 0 - ); - - const totalDeductions = deductions.reduce( - (accumulator, currentValue) => accumulator + currentValue["amount"], - 0 - ); - setTotal(totalEarnings - totalDeductions); - }, [deductions, earnings]); - - const getDevicesDetail = async () => { - setIsLoading(false); - }; - - const handleAddDeduction = () => { - const newDeduction = [...deductions, { deduction_type: "", amount: "" }]; - setDeductions(newDeduction); - }; - - const handleAddEarning = () => { - const newEarning = [...earnings, { earning_type: "", amount: "" }]; - setEarnings(newEarning); - }; - - const handleDeleteDeduction = deduction => { - setDeductions(deductions.filter(d => d !== deduction)); - }; - - const handleDeleteEarning = earning => { - setEarnings(earnings.filter(e => e !== earning)); - }; - - const updateEarningsValues = (earning, event) => { - const { name, value } = event.target; - const updatedEarnings = earnings.map(e => - e == earning ? { ...e, [name]: value } : e - ); - setEarnings(updatedEarnings); - }; - - const updateDeductionValues = (deduction, event) => { - const { name, value } = event.target; - const updatedDeductions = deductions.map(d => - d == deduction ? { ...d, [name]: value } : d - ); - setDeductions(updatedDeductions); - }; - - const handleUpdateDetails = () => { - //Todo: API integration for update details - }; - - const handleCancelDetails = () => { - setIsLoading(true); - navigate(`/settings/compensation`, { replace: true }); - }; - - return ( - -
    - {isLoading ? ( - - ) : ( - - {isDesktop && ( - - )} - {!isDesktop && ( - - )} - - )} - - ); -}; - -export default CompensationEditPage; diff --git a/app/javascript/src/components/Team/Details/CompensationDetails/StaticPage.tsx b/app/javascript/src/components/Team/Details/CompensationDetails/StaticPage.tsx deleted file mode 100644 index 42c702af43..0000000000 --- a/app/javascript/src/components/Team/Details/CompensationDetails/StaticPage.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import React from "react"; - -import { currencyFormat } from "helpers"; -import { EarningsIconSVG, DeductionIconSVG, CoinsIcon } from "miruIcons"; - -const StaticPage = ({ compensationDetails, currency }) => { - const { earnings, deductions, total } = compensationDetails; - - return ( -
    -
    -
    - - - Earnings - -
    -
    - {earnings ? ( - earnings.map((earning, index) => ( -
    -
    - - Earning Type - -

    - {earning.type || "-"} -

    -
    -
    - - Amount - -

    - {currencyFormat(currency, earning.amount) || "-"} -

    -
    -
    - )) - ) : ( -
    No earning(s) found
    - )} -
    -
    -
    -
    - - - Deductions - -
    -
    - {deductions ? ( - deductions.map((deduction, index) => ( -
    -
    - - Deduction Type - -

    - {deduction.type} -

    -
    -
    - - Amount - -

    - {currencyFormat(currency, deduction.amount) || "-"} -

    -
    -
    - )) - ) : ( -
    No deduction(s) found
    - )} -
    -
    -
    -
    - - - Total - -
    -
    - - {currencyFormat(currency, total.amount)} - -
    -
    -
    - ); -}; - -export default StaticPage; diff --git a/app/javascript/src/components/Team/Details/CompensationDetails/index.tsx b/app/javascript/src/components/Team/Details/CompensationDetails/index.tsx deleted file mode 100644 index b7e3eb9ae0..0000000000 --- a/app/javascript/src/components/Team/Details/CompensationDetails/index.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { Fragment, useEffect, useState } from "react"; - -import { useNavigate } from "react-router-dom"; - -import Loader from "common/Loader/index"; -import { MobileEditHeader } from "common/Mobile/MobileEditHeader"; -import { useProfile } from "components/Profile/context/EntryContext"; -import DetailsHeader from "components/Profile/DetailsHeader"; -import { useUserContext } from "context/UserContext"; - -import StaticPage from "./StaticPage"; - -const CompensationDetails = () => { - const { isDesktop, company } = useUserContext(); - const { setUserState, compensationDetails } = useProfile(); - const navigate = useNavigate(); - const [isLoading, setIsLoading] = useState(false); - - const getDetails = async () => { - //fetch compensation details from backend and store it in compensationData - const compensationData = { - earnings: [ - { type: "Monthly Salary", amount: "125000" }, - { type: "SGST (9%)", amount: "11250" }, - { type: "CGST (9%)", amount: "11250" }, - ], - deductions: [{ type: "TDS", amount: "12500" }], - total: { - amount: "147500", - }, - }; - setUserState("compensationDetails", compensationData); - setIsLoading(false); - }; - - useEffect(() => { - setIsLoading(true); - getDetails(); - }, []); - - return ( - - {isDesktop ? ( - - navigate(`/settings/compensation/edit`, { replace: true }) - } - /> - ) : ( - - )} - {isLoading ? ( - - ) : ( - - )} - - ); -}; -export default CompensationDetails; diff --git a/app/javascript/src/components/Team/Details/DeviceDetails/StaticPage.tsx b/app/javascript/src/components/Team/Details/DeviceDetails/StaticPage.tsx deleted file mode 100644 index 3b548b116d..0000000000 --- a/app/javascript/src/components/Team/Details/DeviceDetails/StaticPage.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from "react"; - -const StaticPage = () => ( -
    -
    -
    - - Devices - -
    -
    -
    -
    - - Device Type - -

    Laptop

    -
    -
    - Model -

    - Macbook Pro 13-inch, 2020, Four Thunderbolt 3 ports -

    -
    -
    -
    -
    - - Serial Number - -

    cf4c6be4c742

    -
    -
    - Memory -

    16 GB 3733 MHz LPDDR4X

    -
    -
    -
    -
    - Graphics -

    - Intel Iris Plus Graphics 1536 MB -

    -
    -
    - - Processor - -

    - 2 GHz Quad-Core Intel Core i5 -

    -
    -
    -
    -
    -
    -); - -export default StaticPage; diff --git a/app/javascript/src/components/Team/Details/DeviceDetails/index.tsx b/app/javascript/src/components/Team/Details/DeviceDetails/index.tsx deleted file mode 100644 index 033f11f9a0..0000000000 --- a/app/javascript/src/components/Team/Details/DeviceDetails/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React, { Fragment } from "react"; - -import StaticPage from "./StaticPage"; - -const DeviceDetails = () => ( - -
    -

    Device Details

    -
    - -
    -); - -export default DeviceDetails; diff --git a/app/javascript/src/components/Team/Details/DocumentDetails/index.tsx b/app/javascript/src/components/Team/Details/DocumentDetails/index.tsx deleted file mode 100644 index d0ae3ca09b..0000000000 --- a/app/javascript/src/components/Team/Details/DocumentDetails/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React, { Fragment } from "react"; - -const DocumentDetails = () => ( - -
    -

    Document Details

    -
    -
    - -); - -export default DocumentDetails; diff --git a/app/javascript/src/components/Team/Details/EmploymentDetails/Edit/StaticPage.tsx b/app/javascript/src/components/Team/Details/EmploymentDetails/Edit/StaticPage.tsx deleted file mode 100644 index cb674a7de1..0000000000 --- a/app/javascript/src/components/Team/Details/EmploymentDetails/Edit/StaticPage.tsx +++ /dev/null @@ -1,314 +0,0 @@ -import React from "react"; - -import dayjs from "dayjs"; -import { ProjectsIcon, CalendarIcon, DeleteIcon } from "miruIcons"; -import "react-phone-number-input/style.css"; -import { Button } from "StyledComponents"; - -import CustomDatePicker from "common/CustomDatePicker"; -import { CustomInputText } from "common/CustomInputText"; -import { CustomReactSelect } from "common/CustomReactSelect"; -import { ErrorSpan } from "common/ErrorSpan"; - -const inputClass = - "form__input block w-full appearance-none bg-white p-4 text-base h-12 focus-within:border-miru-han-purple-1000"; - -const labelClass = - "absolute top-0.5 left-1 h-6 z-1 origin-0 bg-white p-2 text-base font-medium duration-300"; - -const StaticPage = ({ - employeeTypes, - employeeType, - handleOnChangeEmployeeType, - updateCurrentEmploymentDetails, - employmentDetails, - showDORDatePicker, - showDOJDatePicker, - setShowDOJDatePicker, - setShowDORDatePicker, - handleDOJDatePicker, - handleDORDatePicker, - errDetails, - DOJRef, - DORRef, - previousEmployments, - handleDeletePreviousEmployment, - handleAddPastEmployment, - updatePreviousEmploymentValues, - dateFormat, - joinedAt, - resignedAt, -}) => { - const getDOJ = joinedAt && dayjs(joinedAt, dateFormat).format(dateFormat); - - const getDOR = resignedAt && dayjs(resignedAt, dateFormat).format(dateFormat); - - return ( -
    -
    -
    - - - Current
    Employment -
    -
    -
    -
    -
    - { - updateCurrentEmploymentDetails(e.target.value, "employee_id"); - }} - /> - {errDetails.employee_id_err && ( - - )} -
    -
    - { - updateCurrentEmploymentDetails(e.target.value, "designation"); - }} - /> - {errDetails.designation_err && ( - - )} -
    -
    -
    -
    - { - updateCurrentEmploymentDetails(e.target.value, "email"); - }} - /> - {errDetails.email_err && ( - - )} -
    -
    - - {errDetails.employment_err && ( - - )} -
    -
    -
    -
    -
    - setShowDOJDatePicker({ - visibility: !showDOJDatePicker.visibility, - }) - } - > - - -
    - {errDetails.joined_at_err && ( - - )} - {showDOJDatePicker.visibility && ( - - )} -
    -
    -
    - setShowDORDatePicker({ - visibility: !showDORDatePicker.visibility, - }) - } - > - - -
    - {errDetails.resigned_at_err && ( - - )} - {showDORDatePicker.visibility && ( - - )} -
    -
    -
    -
    -
    -
    - - - Previous
    Employment -
    -
    -
    - {previousEmployments.length > 0 && - previousEmployments.map((previous, index) => ( -
    -
    -
    - { - updatePreviousEmploymentValues(previous, e); - }} - /> -
    -
    - { - updatePreviousEmploymentValues(previous, e); - }} - /> -
    -
    - -
    - ))} -
    - -
    -
    -
    -
    -
    - ); -}; - -export default StaticPage; diff --git a/app/javascript/src/components/Team/Details/EmploymentDetails/Edit/index.tsx b/app/javascript/src/components/Team/Details/EmploymentDetails/Edit/index.tsx deleted file mode 100644 index 2dc800dc87..0000000000 --- a/app/javascript/src/components/Team/Details/EmploymentDetails/Edit/index.tsx +++ /dev/null @@ -1,330 +0,0 @@ -/* eslint-disable no-unused-vars */ -import React, { Fragment, useEffect, useRef, useState } from "react"; - -import dayjs from "dayjs"; -import utc from "dayjs/plugin/utc"; -import { useOutsideClick } from "helpers"; -import { useNavigate, useParams } from "react-router-dom"; -import * as Yup from "yup"; - -import teamsApi from "apis/teams"; -import Loader from "common/Loader/index"; -import { useTeamDetails } from "context/TeamDetailsContext"; -import { useUserContext } from "context/UserContext"; -import { employmentMapper } from "mapper/teams.mapper"; - -import StaticPage from "./StaticPage"; -import { employmentSchema } from "./validationSchema"; - -dayjs.extend(utc); - -const schema = Yup.object().shape(employmentSchema); - -const EmploymentDetails = () => { - const initialErrState = { - employee_id_err: "", - employment_type_err: "", - email_err: "", - designation_err: "", - joined_at_err: "", - resigned_at_err: "", - company_name_err: "", - role_err: "", - }; - - const { memberId } = useParams(); - const { - updateDetails, - details: { employmentDetails }, - } = useTeamDetails(); - const navigate = useNavigate(); - const { isDesktop } = useUserContext(); - - const DOJRef = useRef(null); - const DORRef = useRef(null); - - const InitialPrevEmployments = { - added_employments: [], - updated_employments: [], - removed_employment_ids: [], - }; - - const [previousEmployments, setPreviousEmployments] = useState([]); - const [employeeType, setEmployeeType] = useState({ label: "", value: "" }); - const [showDOJDatePicker, setShowDOJDatePicker] = useState({ - visibility: false, - }); - - const [showDORDatePicker, setShowDORDatePicker] = useState({ - visibility: false, - }); - const [errDetails, setErrDetails] = useState(initialErrState); - const [isLoading, setIsLoading] = useState(false); - const [dateFormat, setDateFormat] = useState("DD-MM-YYYY"); - const [resignedAt, setResignedAt] = useState(null); - const [joinedAt, setJoinedAt] = useState(null); - - useOutsideClick(DOJRef, () => setShowDOJDatePicker({ visibility: false })); - useOutsideClick(DORRef, () => setShowDORDatePicker({ visibility: false })); - - const employeeTypes = [ - { label: "Salaried Employee", value: "salaried" }, - { label: "Contractor", value: "contractor" }, - ]; - - const getDetails = async () => { - const curr: any = await teamsApi.getEmploymentDetails(memberId); - const prev: any = await teamsApi.getPreviousEmployments(memberId); - setDateFormat(curr.data.date_format); - setJoinedAt(curr.data.employment.joined_at); - setResignedAt(curr.data.employment.resigned_at); - const employmentData = employmentMapper( - curr.data.employment, - prev.data.previous_employments - ); - if (employmentData.current_employment?.employment_type?.length > 0) { - setEmployeeType( - employeeTypes.find( - item => - item.value === employmentData.current_employment.employment_type - ) - ); - } else { - setEmployeeType(employeeTypes[0]); - employmentData.current_employment.employment_type = - employeeTypes[0].value; - } - updateDetails("employment", employmentData); - if (employmentData.previous_employments?.length > 0) { - setPreviousEmployments(employmentData.previous_employments); - } - setIsLoading(false); - }; - - useEffect(() => { - setIsLoading(true); - getDetails(); - }, []); - - const handleOnChangeEmployeeType = empType => { - setEmployeeType(empType); - updateDetails("employment", { - ...employmentDetails, - ...{ - current_employment: { - ...employmentDetails.current_employment, - ...{ employment_type: empType.value }, - }, - }, - }); - }; - - const updateCurrentEmploymentDetails = (value, type) => { - updateDetails("employment", { - ...employmentDetails, - ...{ - current_employment: { - ...employmentDetails.current_employment, - ...{ [type]: value }, - }, - }, - }); - }; - - const updatePreviousEmploymentValues = (previous, event) => { - const { name, value } = event.target; - const updatedPreviousEmployments = previousEmployments.map(prevEmployment => - prevEmployment == previous - ? { ...prevEmployment, [name]: value } - : prevEmployment - ); - setPreviousEmployments(updatedPreviousEmployments); - }; - - const handleDOJDatePicker = date => { - setShowDOJDatePicker({ visibility: !showDOJDatePicker.visibility }); - setJoinedAt(date); - updateDetails("employment", { - ...employmentDetails, - ...{ - current_employment: { - ...employmentDetails.current_employment, - ...{ - joined_at: - dateFormat == "DD-MM-YYYY" - ? date - : dayjs(date).format("DD-MM-YYYY"), - }, - }, - }, - }); - }; - - const handleDORDatePicker = date => { - setShowDORDatePicker({ visibility: !showDORDatePicker.visibility }); - setResignedAt(date); - updateDetails("employment", { - ...employmentDetails, - ...{ - current_employment: { - ...employmentDetails.current_employment, - ...{ - resigned_at: - dateFormat == "DD-MM-YYYY" - ? date - : dayjs(date).format("DD-MM-YYYY"), - }, - }, - }, - }); - }; - - const handleAddPastEmployment = () => { - const pastEmployments = [ - ...previousEmployments, - { company_name: "", role: "" }, - ]; - setPreviousEmployments(pastEmployments); - }; - - const handleDeletePreviousEmployment = previous => { - setPreviousEmployments( - previousEmployments.filter(prev => prev !== previous) - ); - }; - - const handleUpdateDetails = async () => { - setIsLoading(true); - const getDifference = (array1, array2) => - array1.filter(object1 => !array2.some(object2 => object1 === object2)); - - //creating an array which includes removed records - const removed = employmentDetails.previous_employments.filter( - e => !previousEmployments.includes(e) - ); - - //creating an array which includes updated and added records - const unSortedEmployments = getDifference( - previousEmployments, - employmentDetails.previous_employments - ); - - const pastEmployments = InitialPrevEmployments; - - //sorting new entries and updated entries into - unSortedEmployments.map(unSorted => { - if (unSorted.id) { - pastEmployments.updated_employments.push(unSorted); - } else { - pastEmployments.added_employments.push(unSorted); - } - }); - - //Extracting removed records id - if (removed.length > 0) { - removed.map(remove => { - if (pastEmployments.updated_employments.length > 0) { - pastEmployments.updated_employments.filter(updated => { - if (updated.id !== remove.id) { - pastEmployments.removed_employment_ids.push(remove?.id); - } - }); - } else { - pastEmployments.removed_employment_ids.push(remove?.id); - } - }); - } - updateEmploymentDetails(pastEmployments); - }; - - const updateEmploymentDetails = async updatedPreviousEmployments => { - try { - await schema.validate(employmentDetails, { abortEarly: false }); - const payload = { - ...updatedPreviousEmployments, - current_employment: employmentDetails.current_employment, - }; - - await teamsApi.updatePreviousEmployments(memberId, { - employments: payload, - }); - setIsLoading(false); - navigate(`/team/${memberId}/employment`, { replace: true }); - } catch (err) { - setIsLoading(false); - const errObj = initialErrState; - if (err.inner) { - err.inner.map(item => { - if (item.path.includes("current_employment")) { - errObj[`${item.path.split(".").pop()}_err`] = item.message; - } else { - errObj[`${item.path}_err`] = item.message; - } - }); - setErrDetails(errObj); - } - } - }; - - const handleCancelDetails = () => { - setIsLoading(true); - navigate(`/team/${memberId}/employment`, { replace: true }); - }; - - return ( - - {isDesktop && ( - -
    -

    - Employement Details -

    -
    - - -
    -
    - {isLoading ? ( - - ) : ( - - )} -
    - )} -
    - ); -}; - -export default EmploymentDetails; diff --git a/app/javascript/src/components/Team/Details/EmploymentDetails/EmploymentDetailsState.tsx b/app/javascript/src/components/Team/Details/EmploymentDetails/EmploymentDetailsState.tsx deleted file mode 100644 index 6627e53426..0000000000 --- a/app/javascript/src/components/Team/Details/EmploymentDetails/EmploymentDetailsState.tsx +++ /dev/null @@ -1,17 +0,0 @@ -export const EmploymentDetailsState = { - current_employment: { - employee_id: "", - email: "", - employment_type: "", - designation: "", - joined_at: "", - resigned_at: "", - }, - previous_employments: [ - { - company_name: "", - role: "", - id: "", - }, - ], -}; diff --git a/app/javascript/src/components/Team/Details/EmploymentDetails/StaticPage.tsx b/app/javascript/src/components/Team/Details/EmploymentDetails/StaticPage.tsx deleted file mode 100644 index c2862aeda4..0000000000 --- a/app/javascript/src/components/Team/Details/EmploymentDetails/StaticPage.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import React from "react"; - -import { ProjectsIcon } from "miruIcons"; - -const StaticPage = ({ employmentDetails }) => ( -
    -
    -
    - - - Current
    Employment -
    -
    -
    -
    -
    - - Employee ID - -

    - {employmentDetails.current_employment.employee_id} -

    -
    -
    - - Designation - -

    - {employmentDetails.current_employment.designation} -

    -
    -
    -
    -
    - - Email ID (Official) - -

    - {employmentDetails.current_employment.email} -

    -
    -
    - - Employee Type - -

    - {employmentDetails.current_employment.employment_type} -

    -
    -
    -
    -
    - - Date of Joining - -

    - {employmentDetails.current_employment.joined_at} -

    -
    -
    - - Date of Resignation - -

    - {employmentDetails.current_employment.resigned_at} -

    -
    -
    -
    -
    -
    -
    - - - Previous
    Employment -
    -
    -
    - {employmentDetails?.previous_employments[0]?.company_name ? ( - employmentDetails.previous_employments.map((previous, index) => ( -
    -
    - - Company - -

    - {previous.company_name} -

    -
    -
    - Role -

    {previous.role}

    -
    -
    - )) - ) : ( -
    No previous employments found
    - )} -
    -
    -
    -); -export default StaticPage; diff --git a/app/javascript/src/components/Team/Details/EmploymentDetails/index.tsx b/app/javascript/src/components/Team/Details/EmploymentDetails/index.tsx deleted file mode 100644 index 282f979a5a..0000000000 --- a/app/javascript/src/components/Team/Details/EmploymentDetails/index.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, { Fragment, useEffect, useState } from "react"; - -import { useParams, useNavigate } from "react-router-dom"; - -import teamsApi from "apis/teams"; -import Loader from "common/Loader/index"; -import { useTeamDetails } from "context/TeamDetailsContext"; -import { employmentMapper } from "mapper/teams.mapper"; - -import StaticPage from "./StaticPage"; - -const EmploymentDetails = () => { - const { - updateDetails, - details: { employmentDetails }, - } = useTeamDetails(); - const { memberId } = useParams(); - const navigate = useNavigate(); - const [isLoading, setIsLoading] = useState(false); - - const getDetails = async () => { - const res1: any = await teamsApi.getEmploymentDetails(memberId); - const res: any = await teamsApi.getPreviousEmployments(memberId); - const employmentData = employmentMapper( - res1.data.employment, - res.data.previous_employments - ); - updateDetails("employment", employmentData); - setIsLoading(false); - }; - - useEffect(() => { - setIsLoading(true); - getDetails(); - }, []); - - return ( - -
    -

    Employment Details

    - -
    - {isLoading ? ( - - ) : ( - - )} -
    - ); -}; -export default EmploymentDetails; diff --git a/app/javascript/src/components/Team/Details/Layout/Header.tsx b/app/javascript/src/components/Team/Details/Layout/Header.tsx deleted file mode 100644 index ba8b4a393c..0000000000 --- a/app/javascript/src/components/Team/Details/Layout/Header.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from "react"; - -import { ArrowLeftIcon } from "miruIcons"; -import { useNavigate } from "react-router-dom"; - -import { useTeamDetails } from "context/TeamDetailsContext"; - -const Header = () => { - const navigate = useNavigate(); - const { - details: { personalDetails }, - } = useTeamDetails(); - - return ( -
    -
    - -

    - {`${personalDetails.first_name} ${personalDetails.last_name}`} -

    -
    -
    - ); -}; -export default Header; diff --git a/app/javascript/src/components/Team/Details/Layout/MobileNav.tsx b/app/javascript/src/components/Team/Details/Layout/MobileNav.tsx deleted file mode 100644 index cff6cc2a1b..0000000000 --- a/app/javascript/src/components/Team/Details/Layout/MobileNav.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* eslint-disable no-unused-vars */ - -import React from "react"; - -import { UserIcon, ProjectsIcon, MobileIcon } from "miruIcons"; -import { useParams } from "react-router-dom"; - -import withLayout from "common/Mobile/HOC/withLayout"; - -import { TeamUrl } from "./TeamUrl"; -import { UserInformation } from "./UserInformation"; - -const getTeamUrls = memberId => [ - { - url: `/team/${memberId}/details`, - text: "PERSONAL DETAILS", - icon: , - }, - { - url: `/team/${memberId}/employment`, - text: "EMPLOYMENT DETAILS", - icon: , - }, - { - url: "/settings/devices", - text: "ALLOCATED DEVICES", - icon: , - }, -]; - -const MobileNav = () => { - const { memberId } = useParams(); - const urlList = getTeamUrls(memberId); - - const mobileView = () => ( -
    - - -
    - ); - - const DisplayView = withLayout(mobileView, true, true); - - return ; -}; - -export default MobileNav; diff --git a/app/javascript/src/components/Team/Details/Layout/SideNav.tsx b/app/javascript/src/components/Team/Details/Layout/SideNav.tsx deleted file mode 100644 index 2623f4e598..0000000000 --- a/app/javascript/src/components/Team/Details/Layout/SideNav.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import React, { useEffect, useState } from "react"; - -import { DeleteIcon, EditIcon, ImageIcon, UserAvatarSVG } from "miruIcons"; -import { NavLink, useParams } from "react-router-dom"; -import { MoreOptions, Toastr, Tooltip } from "StyledComponents"; - -import teamApi from "apis/team"; -import teamsApi from "apis/teams"; -import { useTeamDetails } from "context/TeamDetailsContext"; - -const getActiveClassName = isActive => { - if (isActive) { - return "pl-4 py-5 border-l-8 border-miru-han-purple-600 bg-miru-gray-200 text-miru-han-purple-600 block"; - } - - return "pl-6 py-5 border-b-1 border-miru-gray-400 block"; -}; - -const getTeamUrls = memberId => [ - { - url: `/team/${memberId}`, - text: "PERSONAL DETAILS", - }, - { - url: `/team/${memberId}/employment`, - text: "EMPLOYMENT DETAILS", - }, - //Todo: Uncomment while API integration - // { - // url: `/team/${memberId}/compensation`, - // text: "COMPENSATION", - // }, -]; - -const UserInformation = ({ memberId }) => { - const { - details: { - personalDetails: { first_name, last_name }, - }, - } = useTeamDetails(); - - const [showProfileOptions, setShowProfileOptions] = useState(false); - const [imageUrl, setImageUrl] = useState(null); - - const getAvatar = async () => { - try { - const responseData = await teamsApi.get(memberId); - setImageUrl(responseData.data.avatar_url); - } catch { - Toastr.error("Error in getting Profile Image"); - } - }; - - useEffect(() => { - getAvatar(); - }, []); - - const validateFileSize = file => { - const sizeInKB = file.size / 1024; - if (sizeInKB > 100) { - throw new Error("Image size needs to be less than 100 KB"); - } - }; - - const createFormData = file => { - const formData = new FormData(); - formData.append("user[avatar]", file); - - return formData; - }; - - const handleProfileImageChange = async e => { - try { - setShowProfileOptions(false); - const file = e.target.files[0]; - validateFileSize(file); - setImageUrl(URL.createObjectURL(file)); - const payload = createFormData(file); - await teamApi.updateTeamMemberAvatar(memberId, payload); - } catch (error) { - Toastr.error(error.message); - } - }; - - const handleDeleteProfileImage = async () => { - try { - setShowProfileOptions(false); - await teamApi.destroyTeamMemberAvatar(memberId); - setImageUrl(null); - Toastr.success("Image deleted successfully"); - } catch { - Toastr.success("Error in deleting Profile Image"); - } - }; - - return ( -
    -
    -
    -
    -
    - - -
    -
    -
    - {showProfileOptions && ( - -
  • - - - {imageUrl && ( -
  • - - Delete -
  • - )} - -
    - )} - -
    - - {`${first_name} ${last_name}`} - -
    -
    - -
    -
    -
    - ); -}; - -const TeamUrl = ({ urlList }) => ( -
    -
      - {urlList.map((item, index) => ( -
    • - getActiveClassName(isActive)} - to={item.url} - > - {item.text} - -
    • - ))} -
    -
    -); - -const SideNav = () => { - const { memberId } = useParams(); - const urlList = getTeamUrls(memberId); - - return ( -
    - - -
    - ); -}; - -export default SideNav; diff --git a/app/javascript/src/components/Team/Details/Layout/TeamUrl.tsx b/app/javascript/src/components/Team/Details/Layout/TeamUrl.tsx deleted file mode 100644 index 8c905aedcb..0000000000 --- a/app/javascript/src/components/Team/Details/Layout/TeamUrl.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from "react"; - -import { RightArrowIcon } from "miruIcons"; -import { NavLink } from "react-router-dom"; - -const getActiveClassName = isActive => { - if (isActive) { - return "pl-4 py-5 border-l-8 border-miru-han-purple-600 bg-miru-gray-200 text-miru-han-purple-600 block"; - } - - return "pl-6 py-5 border-b-1 border-miru-gray-400 block"; -}; - -export const TeamUrl = ({ urlList }) => ( -
    -
      - {urlList.map((item, index) => ( -
    • - getActiveClassName(isActive)} - to={item.url} - > -
      -
      - {item.icon} - {item.text} -
      -
      - -
      -
      -
      -
    • - ))} -
    -
    -); diff --git a/app/javascript/src/components/Team/Details/Layout/UserInformation.tsx b/app/javascript/src/components/Team/Details/Layout/UserInformation.tsx deleted file mode 100644 index d471a8f269..0000000000 --- a/app/javascript/src/components/Team/Details/Layout/UserInformation.tsx +++ /dev/null @@ -1,150 +0,0 @@ -/* eslint-disable no-unused-vars */ - -import React, { useEffect, useState } from "react"; - -import { UserAvatarSVG, DeleteIcon, ImageIcon, EditIcon } from "miruIcons"; -import { useParams } from "react-router-dom"; -import { MobileMoreOptions, Toastr, Tooltip } from "StyledComponents"; - -import teamApi from "apis/team"; -import teamsApi from "apis/teams"; -import { useTeamDetails } from "context/TeamDetailsContext"; -import { teamsMapper } from "mapper/teams.mapper"; - -export const UserInformation = () => { - const [showImageUpdateOptions, setShowImageUpdateOptions] = - useState(false); - const [userImageUrl, setUserImageUrl] = useState(null); - const { - details: { personalDetails }, - updateDetails, - } = useTeamDetails(); - const { memberId } = useParams(); - - const getDetails = async () => { - try { - const res: any = await teamsApi.get(memberId); - const addRes = await teamsApi.getAddress(memberId); - const teamsObj = teamsMapper(res.data, addRes.data.addresses[0]); - updateDetails("personal", teamsObj); - } catch { - Toastr.error("Something went wrong"); - } - }; - - const validateFileSize = file => { - const sizeInKB = file.size / 1024; - if (sizeInKB > 100) { - throw new Error("Image size needs to be less than 100 KB"); - } - }; - - const createFormData = file => { - const formData = new FormData(); - formData.append("user[avatar]", file); - - return formData; - }; - - const handleProfileImageChange = async e => { - try { - setShowImageUpdateOptions(false); - const file = e.target.files[0]; - validateFileSize(file); - setUserImageUrl(URL.createObjectURL(file)); - const payload = createFormData(file); - await teamApi.updateTeamMemberAvatar(memberId, payload); - } catch (error) { - Toastr.error(error.message); - } - }; - - const handleDeleteProfileImage = async () => { - setShowImageUpdateOptions(false); - await teamApi.destroyTeamMemberAvatar(memberId); - setUserImageUrl(null); - }; - - const getAvatar = async () => { - const responseData = await teamsApi.get(memberId); - setUserImageUrl(responseData.data.avatar_url); - }; - - useEffect(() => { - getAvatar(); - getDetails(); - }, []); - - return ( -
    -
    -
    - - -
    - {showImageUpdateOptions ? ( - -
  • - - -
  • - {userImageUrl && ( -
  • -
    - -
    -

    - Delete -

    -
  • - )} -
    - ) : null} -
    - -
    - - {`${personalDetails.first_name} ${personalDetails.last_name}`} - -
    -
    - -
    -
    -
    - ); -}; diff --git a/app/javascript/src/components/Team/Details/PersonalDetails/Edit/MobileEditPage.tsx b/app/javascript/src/components/Team/Details/PersonalDetails/Edit/MobileEditPage.tsx deleted file mode 100644 index 323eb3143a..0000000000 --- a/app/javascript/src/components/Team/Details/PersonalDetails/Edit/MobileEditPage.tsx +++ /dev/null @@ -1,427 +0,0 @@ -import React from "react"; - -import dayjs from "dayjs"; -import { - CalendarIcon, - GlobeIcon, - InfoIcon, - MapPinIcon, - PhoneIcon, -} from "miruIcons"; -import PhoneInput from "react-phone-number-input"; -import flags from "react-phone-number-input/flags"; -import "react-phone-number-input/style.css"; - -import CustomDatePicker from "common/CustomDatePicker"; -import { CustomInputText } from "common/CustomInputText"; -import { CustomReactSelect } from "common/CustomReactSelect"; -import { Divider } from "common/Divider"; -import { ErrorSpan } from "common/ErrorSpan"; - -const inputClass = - "form__input block w-full appearance-none bg-white p-4 text-sm h-12 focus-within:border-miru-han-purple-1000"; - -const labelClass = - "absolute top-0.5 left-1 h-6 z-1 origin-0 bg-white p-2 text-sm font-medium duration-300"; - -const MobileEditDetails = ({ - addrType, - addressOptions, - countries, - handleOnChangeAddrType, - handleOnChangeCountry, - handleCancelDetails, - handleUpdateDetails, - updateBasicDetails, - personalDetails, - showDatePicker, - setShowDatePicker, - handleDatePicker, - errDetails, - handlePhoneNumberChange, - wrapperRef, - dateFormat, -}) => ( -
    -
    - - Basic Details - -
    -
    - { - updateBasicDetails(e.target.value, "first_name", false, ""); - }} - /> - {errDetails.first_name_err && ( - - )} -
    -
    - { - updateBasicDetails(e.target.value, "last_name", false, ""); - }} - /> - {errDetails.last_name_err && ( - - )} -
    -
    -
    -
    -
    - setShowDatePicker({ visibility: !showDatePicker.visibility }) - } - > - { - updateBasicDetails(e.target.value, "date_of_birth", false); - }} - /> - -
    - {showDatePicker.visibility && ( - handleDatePicker(e, true)} - date={ - personalDetails.date_of_birth - ? personalDetails.date_of_birth - : dayjs() - } - /> - )} -
    -
    -
    - -
    - - Contact - Details - -
    -
    -
    - - -
    -
    -
    - { - updateBasicDetails(e.target.value, "email_id", false); - }} - /> - {errDetails.email_id_err && ( - - )} -
    -
    -
    - -
    - - Address - -
    -
    - -
    -
    - { - updateBasicDetails(e.target.value, "address_line_1", true); - }} - /> - {errDetails.address_line_1_err && ( - - )} -
    -
    - { - updateBasicDetails(e.target.value, "address_line_2", true); - }} - /> -
    -
    -
    - handleOnChangeCountry(value)} - isErr={!!errDetails.country_err} - label="Country" - name="current_country_select" - options={countries} - value={{ - label: personalDetails.addresses.country, - value: personalDetails.addresses.country, - }} - /> - {errDetails.country_err && ( - - )} -
    -
    - { - updateBasicDetails(e.target.value, "state", true); - }} - /> - {errDetails.state_err && ( - - )} -
    -
    -
    -
    - { - updateBasicDetails(e.target.value, "city", true); - }} - /> - {errDetails.city_err && ( - - )} -
    -
    - { - updateBasicDetails(e.target.value, "pin", true); - }} - /> - {errDetails.pin_err && ( - - )} -
    -
    -
    -
    - -
    - - - Social Profiles - -
    -
    - { - updateBasicDetails(e.target.value, "linkedin", false, ""); - }} - /> -
    -
    - { - updateBasicDetails(e.target.value, "github", false, ""); - }} - /> -
    -
    -
    - -
    - - -
    -
    -); - -export default MobileEditDetails; diff --git a/app/javascript/src/components/Team/Details/PersonalDetails/Edit/StaticPage.tsx b/app/javascript/src/components/Team/Details/PersonalDetails/Edit/StaticPage.tsx deleted file mode 100644 index 2678781432..0000000000 --- a/app/javascript/src/components/Team/Details/PersonalDetails/Edit/StaticPage.tsx +++ /dev/null @@ -1,417 +0,0 @@ -import React from "react"; - -import dayjs from "dayjs"; -import { - GlobeIcon, - CalendarIcon, - PhoneIcon, - MapPinIcon, - InfoIcon, -} from "miruIcons"; -import PhoneInput from "react-phone-number-input"; -import flags from "react-phone-number-input/flags"; -import "react-phone-number-input/style.css"; - -import CustomDatePicker from "common/CustomDatePicker"; -import { CustomInputText } from "common/CustomInputText"; -import { CustomReactSelect } from "common/CustomReactSelect"; -import { ErrorSpan } from "common/ErrorSpan"; - -const inputClass = - "form__input block w-full appearance-none bg-white p-4 text-base h-12 focus-within:border-miru-han-purple-1000"; - -const labelClass = - "absolute top-0.5 left-1 h-6 z-1 origin-0 bg-white p-2 text-base font-medium duration-300"; - -const StaticPage = ({ - addressOptions, - addrType, - countries, - handleOnChangeAddrType, - handleOnChangeCountry, - updateBasicDetails, - personalDetails, - showDatePicker, - setShowDatePicker, - handleDatePicker, - errDetails, - handlePhoneNumberChange, - wrapperRef, - dateFormat, -}) => ( -
    -
    -
    - - Basic - Details - -
    -
    -
    -
    - { - updateBasicDetails(e.target.value, "first_name", false, ""); - }} - /> - {errDetails.first_name_err && ( - - )} -
    -
    - { - updateBasicDetails(e.target.value, "last_name", false, ""); - }} - /> - {errDetails.last_name_err && ( - - )} -
    -
    -
    -
    - setShowDatePicker({ visibility: !showDatePicker.visibility }) - } - > - { - updateBasicDetails(e.target.value, "date_of_birth", false); - }} - /> - -
    - {showDatePicker.visibility && ( - handleDatePicker(e, true)} - date={ - personalDetails.date_of_birth - ? personalDetails.date_of_birth - : dayjs() - } - /> - )} -
    -
    -
    -
    -
    - - - Contact Details - -
    -
    -
    -
    -
    - - -
    -
    -
    - { - updateBasicDetails(e.target.value, "email_id", false); - }} - /> - {errDetails.email_id_err && ( - - )} -
    -
    -
    -
    -
    -
    - - - Address - -
    -
    -
    -
    - -
    -
    -
    - { - updateBasicDetails(e.target.value, "address_line_1", true); - }} - /> - {errDetails.address_line_1_err && ( - - )} -
    -
    - { - updateBasicDetails(e.target.value, "address_line_2", true); - }} - /> -
    -
    -
    - handleOnChangeCountry(value)} - isErr={!!errDetails.country_err} - label="Country" - name="current_country_select" - options={countries} - value={{ - label: personalDetails.addresses.country, - value: personalDetails.addresses.country, - }} - /> - {errDetails.country_err && ( - - )} -
    -
    - { - updateBasicDetails(e.target.value, "state", true); - }} - /> - {errDetails.state_err && ( - - )} -
    -
    -
    -
    - { - updateBasicDetails(e.target.value, "city", true); - }} - /> - {errDetails.city_err && ( - - )} -
    -
    - { - updateBasicDetails(e.target.value, "pin", true); - }} - /> - {errDetails.pin_err && ( - - )} -
    -
    -
    -
    -
    -
    - - - Social Profiles - -
    -
    -
    -
    - { - updateBasicDetails(e.target.value, "linkedin", false); - }} - /> -
    -
    - { - updateBasicDetails(e.target.value, "github", false); - }} - /> -
    -
    -
    -
    -
    -); - -export default StaticPage; diff --git a/app/javascript/src/components/Team/Details/PersonalDetails/Edit/index.tsx b/app/javascript/src/components/Team/Details/PersonalDetails/Edit/index.tsx deleted file mode 100644 index 2d180199e5..0000000000 --- a/app/javascript/src/components/Team/Details/PersonalDetails/Edit/index.tsx +++ /dev/null @@ -1,303 +0,0 @@ -/* eslint-disable no-unused-vars */ -import React, { Fragment, useEffect, useRef, useState } from "react"; - -import { Country } from "country-state-city"; -import dayjs from "dayjs"; -import utc from "dayjs/plugin/utc"; -import { useOutsideClick } from "helpers"; -import { useNavigate, useParams } from "react-router-dom"; -import * as Yup from "yup"; - -import teamsApi from "apis/teams"; -import Loader from "common/Loader/index"; -import { MobileDetailsHeader } from "common/Mobile/MobileDetailsHeader"; -import { useTeamDetails } from "context/TeamDetailsContext"; -import { useUserContext } from "context/UserContext"; -import { teamsMapper } from "mapper/teams.mapper"; - -import MobileEditPage from "./MobileEditPage"; -import StaticPage from "./StaticPage"; -import { userSchema } from "./validationSchema"; - -dayjs.extend(utc); - -const addressOptions = [ - { label: "Current", value: "current" }, - { label: "Permanent", value: "permanent" }, -]; - -const schema = Yup.object().shape(userSchema); - -const EmploymentDetails = () => { - const initialErrState = { - first_name_err: "", - last_name_err: "", - address_line_1_err: "", - country_err: "", - state_err: "", - city_err: "", - email_id_err: "", - pin_err: "", - }; - - const { memberId } = useParams(); - const { - updateDetails, - details: { personalDetails }, - } = useTeamDetails(); - const navigate = useNavigate(); - const { isDesktop } = useUserContext(); - const wrapperRef = useRef(null); - - const [addrType, setAddrType] = useState({ label: "", value: "" }); - const [showDatePicker, setShowDatePicker] = useState({ visibility: false }); - const [countries, setCountries] = useState([]); - const [errDetails, setErrDetails] = useState(initialErrState); - const [isLoading, setIsLoading] = useState(false); - const [addrId, setAddrId] = useState(); - - useOutsideClick(wrapperRef, () => setShowDatePicker({ visibility: false })); - - const assignCountries = async allCountries => { - const countryData = await allCountries.map(country => ({ - value: country.name, - label: country.name, - code: country.isoCode, - })); - setCountries(countryData); - }; - - const getDetails = async () => { - const res: any = await teamsApi.get(memberId); - const addRes = await teamsApi.getAddress(memberId); - const teamsObj = teamsMapper(res.data, addRes.data.addresses[0]); - updateDetails("personal", teamsObj); - if (teamsObj.addresses?.address_type?.length > 0) { - setAddrType( - addressOptions.find( - item => item.value === teamsObj.addresses.address_type - ) - ); - } - setAddrId(addRes.data.addresses[0]?.id); - setIsLoading(false); - }; - - useEffect(() => { - setIsLoading(true); - const allCountries = Country.getAllCountries(); - assignCountries(allCountries); - getDetails(); - }, []); - - const handleOnChangeCountry = selectCountry => { - updateDetails("personal", { - ...personalDetails, - ...{ - addresses: { - ...personalDetails.addresses, - ...{ country: selectCountry.value, state: "", city: "" }, - }, - }, - }); - }; - - const handleOnChangeAddrType = addreType => { - setAddrType(addreType); - updateDetails("personal", { - ...personalDetails, - ...{ - addresses: { - ...personalDetails.addresses, - ...{ address_type: addreType.value }, - }, - }, - }); - }; - - const updateBasicDetails = (value, type, isAddress = false) => { - if (isAddress) { - updateDetails("personal", { - ...personalDetails, - ...{ - addresses: { ...personalDetails.addresses, ...{ [type]: value } }, - }, - }); - } else { - updateDetails("personal", { - ...personalDetails, - ...{ [type]: value }, - }); - } - }; - - const handleDatePicker = date => { - setShowDatePicker({ visibility: !showDatePicker.visibility }); - const formattedDate = dayjs(date, personalDetails.date_format).format( - personalDetails.date_format - ); - - updateDetails("personal", { - ...personalDetails, - ...{ date_of_birth: formattedDate }, - }); - }; - - const handleUpdateDetails = async () => { - try { - await schema.validate( - { - ...personalDetails, - ...{ - is_email: personalDetails.email_id - ? personalDetails.email_id.length > 0 - : false, - }, - }, - { abortEarly: false } - ); - - await teamsApi.updateUser(memberId, { - user: { - first_name: personalDetails.first_name, - last_name: personalDetails.last_name, - date_of_birth: personalDetails.date_of_birth - ? dayjs - .utc(personalDetails.date_of_birth, personalDetails.date_format) - .toISOString() - : null, - phone: personalDetails.phone_number || "", - personal_email_id: personalDetails.email_id, - social_accounts: { - linkedin_url: personalDetails.linkedin, - github_url: personalDetails.github, - }, - }, - }); - - const payload = { - address: { - address_line_1: personalDetails.addresses.address_line_1, - address_line_2: personalDetails.addresses.address_line_2, - address_type: personalDetails.addresses.address_type, - city: personalDetails.addresses.city, - state: personalDetails.addresses.state, - country: personalDetails.addresses.country, - pin: personalDetails.addresses.pin, - }, - }; - if (addrId) { - await teamsApi.updateAddress(memberId, addrId, { - address: { ...personalDetails.addresses }, - }); - } else { - await teamsApi.createAddress(memberId, payload); - } - - setErrDetails(initialErrState); - navigate(`/team/${memberId}`, { replace: true }); - } catch (err) { - setIsLoading(false); - const errObj = initialErrState; - if (err.inner) { - err.inner.map(item => { - if (item.path.includes("addresses")) { - errObj[`${item.path.split(".").pop()}_err`] = item.message; - } else { - errObj[`${item.path}_err`] = item.message; - } - }); - setErrDetails(errObj); - } - } - }; - - const handlePhoneNumberChange = phoneNumber => { - updateBasicDetails(phoneNumber, "phone_number", false); - }; - - const handleCancelDetails = () => { - setIsLoading(true); - navigate(`/team/${memberId}`, { replace: true }); - }; - - return ( - - {isDesktop && ( - -
    -

    Personal Details

    -
    - - -
    -
    - {isLoading ? ( - - ) : ( - - )} -
    - )} - {!isDesktop && ( - - - {isLoading ? ( -
    - -
    - ) : ( - - )} -
    - )} -
    - ); -}; - -export default EmploymentDetails; diff --git a/app/javascript/src/components/Team/Details/PersonalDetails/Edit/validationSchema.ts b/app/javascript/src/components/Team/Details/PersonalDetails/Edit/validationSchema.ts deleted file mode 100644 index 39c3640535..0000000000 --- a/app/javascript/src/components/Team/Details/PersonalDetails/Edit/validationSchema.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as Yup from "yup"; - -export const userSchema = { - first_name: Yup.string() - .required("Please enter first name") - .max(20, "Maximum 20 characters are allowed"), - last_name: Yup.string() - .required("Please enter last name") - .max(20, "Maximum 20 characters are allowed"), - addresses: Yup.object().shape({ - address_line_1: Yup.string().required("Please enter address line 1"), - country: Yup.string().required("Please enter country"), - state: Yup.string().required("Please enter state"), - city: Yup.string().required("Please enter city"), - pin: Yup.string().required("Please enter zipcode"), - }), - is_email: Yup.boolean(), - email_id: Yup.string() - .nullable() - .when("is_email", { - is: true, - then: Yup.string().email("Please enter valid email"), - }), -}; diff --git a/app/javascript/src/components/Team/Details/PersonalDetails/MobilePersonalDetails.tsx b/app/javascript/src/components/Team/Details/PersonalDetails/MobilePersonalDetails.tsx deleted file mode 100644 index 107f7f70a5..0000000000 --- a/app/javascript/src/components/Team/Details/PersonalDetails/MobilePersonalDetails.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import React from "react"; - -import dayjs from "dayjs"; -import { GlobeIcon, InfoIcon, MapPinIcon, PhoneIcon } from "miruIcons"; - -import { Divider } from "common/Divider"; -import { InfoDescription } from "common/Mobile/InfoDescription"; - -const MobilePersonalDetails = ({ - personalDetails: { - first_name, - last_name, - date_of_birth, - phone_number, - email_id, - addresses, - linkedin, - github, - date_format, - }, -}) => ( -
    -
    - - Basic Details - -
    -
    - -
    -
    - -
    -
    -
    - -
    - - Contact - Details - -
    -
    - -
    -
    - -
    -
    -
    - -
    - - Address - -
    -
    - {addresses && ( - - )} -
    -
    -
    - -
    - - - Social Profiles - -
    -
    - -
    -
    - -
    -
    -
    -
    -); - -export default MobilePersonalDetails; diff --git a/app/javascript/src/components/Team/Details/PersonalDetails/PersonalDetailsState.tsx b/app/javascript/src/components/Team/Details/PersonalDetails/PersonalDetailsState.tsx deleted file mode 100644 index 13cd48ed6a..0000000000 --- a/app/javascript/src/components/Team/Details/PersonalDetails/PersonalDetailsState.tsx +++ /dev/null @@ -1,20 +0,0 @@ -export const PersonalDetailsState = { - first_name: "", - last_name: "", - date_of_birth: "", - phone_number: "", - email_id: "", - addresses: { - id: "", - address_type: "", - address_line_1: "", - address_line_2: "", - country: "", - state: "", - city: "", - pin: "", - }, - linkedin: "", - github: "", - date_format: "", -}; diff --git a/app/javascript/src/components/Team/Details/PersonalDetails/StaticPage.tsx b/app/javascript/src/components/Team/Details/PersonalDetails/StaticPage.tsx deleted file mode 100644 index bb41d5b976..0000000000 --- a/app/javascript/src/components/Team/Details/PersonalDetails/StaticPage.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import React from "react"; - -import dayjs from "dayjs"; -import customParseFormat from "dayjs/plugin/customParseFormat"; -import { GlobeIcon, InfoIcon, MapPinIcon, PhoneIcon } from "miruIcons"; - -dayjs.extend(customParseFormat); - -const StaticPage = ({ personalDetails }) => ( -
    -
    -
    - - Basic - Details - -
    -
    -
    -
    - Name -

    - {personalDetails.first_name} {personalDetails.last_name} -

    -
    -
    - - Date of Birth - -

    - {personalDetails.date_of_birth && - dayjs( - personalDetails.date_of_birth, - personalDetails.date_format - ).format(personalDetails.date_format)} -

    -
    -
    -
    -
    -
    -
    - - - Contact Details - -
    -
    -
    -
    - - Phone Number - -

    - {personalDetails.phone_number} -

    -
    -
    - - Email ID (Personal) - -

    - {personalDetails.email_id} -

    -
    -
    -
    -
    -
    -
    - - - Address - -
    -
    -
    -
    - Address -

    - {personalDetails.addresses && ( - <> - {personalDetails.addresses.address_line_1}, - {personalDetails.addresses.address_line_2} - {personalDetails.addresses.city}, - {personalDetails.addresses.state}, - {personalDetails.addresses.country} - - {personalDetails.addresses.pin} - - )} -

    -
    -
    -
    -
    -
    -
    - - - Social Profiles - -
    -
    -
    -
    - LinkedIn -

    - {personalDetails.linkedin} -

    -
    -
    - Github -

    - {personalDetails.github} -

    -
    -
    -
    -
    -
    -); - -export default StaticPage; diff --git a/app/javascript/src/components/Team/Details/PersonalDetails/index.tsx b/app/javascript/src/components/Team/Details/PersonalDetails/index.tsx deleted file mode 100644 index ba5f00db75..0000000000 --- a/app/javascript/src/components/Team/Details/PersonalDetails/index.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import React, { Fragment, useEffect, useState } from "react"; - -import { Outlet, useParams, useNavigate } from "react-router-dom"; - -import teamsApi from "apis/teams"; -import Loader from "common/Loader/index"; -import { MobileEditHeader } from "common/Mobile/MobileEditHeader"; -import { useTeamDetails } from "context/TeamDetailsContext"; -import { useUserContext } from "context/UserContext"; -import { teamsMapper } from "mapper/teams.mapper"; - -import MobilePersonalDetails from "./MobilePersonalDetails"; -import StaticPage from "./StaticPage"; - -const PersonalDetails = () => { - const { memberId } = useParams(); - const { isDesktop } = useUserContext(); - const { - updateDetails, - details: { personalDetails }, - } = useTeamDetails(); - const [isLoading, setIsLoading] = useState(false); - const navigate = useNavigate(); - - const getDetails = async () => { - const res: any = await teamsApi.get(memberId); - const addRes = await teamsApi.getAddress(memberId); - const teamsObj = teamsMapper(res.data, addRes.data.addresses[0]); - updateDetails("personal", teamsObj); - setIsLoading(false); - }; - - useEffect(() => { - setIsLoading(true); - getDetails(); - }, []); - - return ( - - {isDesktop && ( - -
    -

    Personal Details

    - -
    - {isLoading ? ( - - ) : ( - - )} -
    - )} - {!isDesktop && ( - - - {isLoading ? ( - - ) : ( - - )} - - )} - -
    - ); -}; - -export default PersonalDetails; diff --git a/app/javascript/src/components/Team/Details/ReimburstmentDetails/StaticPage.tsx b/app/javascript/src/components/Team/Details/ReimburstmentDetails/StaticPage.tsx deleted file mode 100644 index dd1144e016..0000000000 --- a/app/javascript/src/components/Team/Details/ReimburstmentDetails/StaticPage.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from "react"; - -import { PenIcon, DeleteIcon } from "miruIcons"; - -const StaticPage = () => ( -
    - - - - - - - - - - - - - - - - - - - - - - - - - -
    DATEDESCRIPTIONSTORE/MODEAMOUNT -
    22.04.2022 - Medium annual team subscription - Online/Netbanking₹7,500 - - -
    22.04.2022 - Medium annual team subscription - Online/Netbanking₹7,500 - - -
    -
    -); - -export default StaticPage; diff --git a/app/javascript/src/components/Team/Details/ReimburstmentDetails/index.tsx b/app/javascript/src/components/Team/Details/ReimburstmentDetails/index.tsx deleted file mode 100644 index d8bb67d259..0000000000 --- a/app/javascript/src/components/Team/Details/ReimburstmentDetails/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React, { Fragment } from "react"; - -import StaticPage from "./StaticPage"; - -const ReimburstmentDetails = () => ( - -
    -

    Reimburstment Details

    -
    - -
    -); - -export default ReimburstmentDetails; diff --git a/app/javascript/src/components/Team/Details/index.tsx b/app/javascript/src/components/Team/Details/index.tsx deleted file mode 100644 index 2b92994079..0000000000 --- a/app/javascript/src/components/Team/Details/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React, { Fragment, useState } from "react"; - -import { TeamDetailsContext } from "context/TeamDetailsContext"; -import { useUserContext } from "context/UserContext"; - -import { CompensationDetailsState } from "./CompensationDetails/CompensationDetailsState"; -import { EmploymentDetailsState } from "./EmploymentDetails/EmploymentDetailsState"; -import Header from "./Layout/Header"; -import OutletWrapper from "./Layout/OutletWrapper"; -import SideNav from "./Layout/SideNav"; -import { PersonalDetailsState } from "./PersonalDetails/PersonalDetailsState"; - -const TeamDetails = () => { - const [details, setDetails] = useState({ - personalDetails: PersonalDetailsState, - employmentDetails: EmploymentDetailsState, - documentDetails: {}, - deviceDetails: {}, - compensationDetails: CompensationDetailsState, - reimburstmentDetails: {}, - }); - const { isDesktop } = useUserContext(); - const updateDetails = (key, payload) => { - setDetails({ ...details, [`${key}Details`]: payload }); - }; - - return ( - - {isDesktop && ( - -
    -
    -
    - -
    -
    - -
    -
    - - )} - {!isDesktop && } - - ); -}; - -export default TeamDetails; diff --git a/app/javascript/src/components/Team/List/Table/TableRow.tsx b/app/javascript/src/components/Team/List/Table/TableRow.tsx index f857cb62c0..76c6ad8f91 100644 --- a/app/javascript/src/components/Team/List/Table/TableRow.tsx +++ b/app/javascript/src/components/Team/List/Table/TableRow.tsx @@ -48,7 +48,7 @@ const TableRow = ({ item }) => { const handleRowClick = () => { if (!status) return; - navigate(`/team/${id}`, { replace: true }); + navigate(`/team/${id}/profile`, { replace: true }); }; return ( diff --git a/app/javascript/src/components/Team/RouteConfig.tsx b/app/javascript/src/components/Team/RouteConfig.tsx deleted file mode 100644 index 1fe95241f1..0000000000 --- a/app/javascript/src/components/Team/RouteConfig.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; - -import { Route, Routes } from "react-router-dom"; - -import Details from "./Details"; -import CompensationDetails from "./Details/CompensationDetails"; -import CompensationEdit from "./Details/CompensationDetails/Edit"; -import EmploymentDetails from "./Details/EmploymentDetails"; -import EmploymentEdit from "./Details/EmploymentDetails/Edit"; -import MobileNav from "./Details/Layout/MobileNav"; -import PersonalDetails from "./Details/PersonalDetails"; -import PersonalEdit from "./Details/PersonalDetails/Edit"; - -const RouteConfig = () => ( - - } path=":memberId"> - } /> - } path="edit" /> - } path="options" /> - } path="details" /> - } path="employment" /> - } path="employment_edit" /> - } path="compensation" /> - } path="compensation_edit" /> - - -); - -export default RouteConfig; diff --git a/app/javascript/src/constants/routes.ts b/app/javascript/src/constants/routes.ts index ced02d73a3..15f30499e1 100644 --- a/app/javascript/src/constants/routes.ts +++ b/app/javascript/src/constants/routes.ts @@ -10,6 +10,7 @@ import InvoiceEmail from "components/InvoiceEmail"; import InvoicesRouteConfig from "components/Invoices/InvoicesRouteConfig"; import LeaveManagement from "components/LeaveManagement"; import Success from "components/payments/Success"; +import ProfileRouteConfig from "components/Profile/Layout/RouteConfig"; import Projects from "components/Projects"; import AccountsAgingReport from "components/Reports/AccountsAgingReport"; import InvalidLink from "components/Team/List/InvalidLink"; @@ -19,7 +20,6 @@ import { Roles, Paths } from "constants/index"; import Clients from "../components/Clients"; import ClientDetails from "../components/Clients/Details"; import Payments from "../components/payments"; -import ProfileLayout from "../components/Profile/Layout"; import ProjectDetails from "../components/Projects/Details"; import ReportList from "../components/Reports/List"; import OutstandingInvoiceReport from "../components/Reports/OutstandingInvoiceReport"; @@ -27,7 +27,6 @@ import RevenueByClientReport from "../components/Reports/RevenueByClientReport"; import TimeEntryReports from "../components/Reports/TimeEntryReport"; import TotalHoursReport from "../components/Reports/totalHoursLogged"; import PlanSelection from "../components/Subscriptions/PlanSelection"; -import RouteConfig from "../components/Team/RouteConfig"; import TimesheetEntries from "../components/TimesheetEntries"; const ClientsRoutes = [ @@ -70,13 +69,13 @@ const LeaveManagementRoutes = [ { path: "*", Component: ErrorPage }, ]; -const TeamRoutes = [{ path: "*", Component: RouteConfig }]; +const TeamRoutes = [{ path: "*", Component: ProfileRouteConfig }]; const TeamsRoutes = [{ path: "*", Component: TeamsRouteConfig }]; const InvoiceRoutes = [{ path: "*", Component: InvoicesRouteConfig }]; -const SettingsRoutes = [{ path: "*", Component: ProfileLayout }]; +const SettingsRoutes = [{ path: "*", Component: ProfileRouteConfig }]; const ExpenseRoutes = [ { path: "", Component: Expenses }, diff --git a/app/javascript/src/context/Profile/ProfileContext.tsx b/app/javascript/src/context/Profile/ProfileContext.tsx new file mode 100644 index 0000000000..10cf333c1b --- /dev/null +++ b/app/javascript/src/context/Profile/ProfileContext.tsx @@ -0,0 +1,23 @@ +import { createContext, useContext } from "react"; + +import { CompensationDetailsState } from "components/Profile/Context/CompensationDetailsState"; +import { EmploymentDetailsState } from "components/Profile/Context/EmploymentDetailsState"; +import { PersonalDetailsState } from "components/Profile/Context/PersonalDetailsState"; +// Context Creation + +export const ProfileContext = createContext({ + personalDetails: PersonalDetailsState, + employmentDetails: EmploymentDetailsState, + documentDetails: {}, + deviceDetails: {}, + compensationDetails: CompensationDetailsState, + reimburstmentDetails: {}, + updateDetails: (key, payload) => {}, //eslint-disable-line + isCalledFromSettings: false, + setIsCalledFromSettings: val => {}, //eslint-disable-line + isCalledFromTeam: false, + setIsCalledFromTeam: val => {}, //eslint-disable-line +}); + +// Custom Hooks +export const useProfileContext = () => useContext(ProfileContext); diff --git a/app/javascript/src/context/TeamDetailsContext.tsx b/app/javascript/src/context/TeamDetailsContext.tsx deleted file mode 100644 index d9467d3537..0000000000 --- a/app/javascript/src/context/TeamDetailsContext.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { createContext, useContext } from "react"; - -import { CompensationDetailsState } from "components/Team/Details/CompensationDetails/CompensationDetailsState"; -import { EmploymentDetailsState } from "components/Team/Details/EmploymentDetails/EmploymentDetailsState"; -import { PersonalDetailsState } from "components/Team/Details/PersonalDetails/PersonalDetailsState"; -// Context Creation - -export const TeamDetailsContext = createContext({ - details: { - personalDetails: PersonalDetailsState, - employmentDetails: EmploymentDetailsState, - documentDetails: {}, - deviceDetails: {}, - compensationDetails: CompensationDetailsState, - reimburstmentDetails: {}, - }, - updateDetails: (key, payload) => {}, //eslint-disable-line -}); - -// Custom Hooks -export const useTeamDetails = () => useContext(TeamDetailsContext); diff --git a/app/javascript/src/mapper/teams.mapper.ts b/app/javascript/src/mapper/teams.mapper.ts index 015f06d44c..bf1fbabf33 100644 --- a/app/javascript/src/mapper/teams.mapper.ts +++ b/app/javascript/src/mapper/teams.mapper.ts @@ -1,6 +1,7 @@ import dayjs from "dayjs"; export const teamsMapper = (user, address) => ({ + id: user.id, first_name: user.first_name, last_name: user.last_name, date_of_birth: diff --git a/app/policies/profile_policy.rb b/app/policies/profile_policy.rb index 9c840768c2..6733733109 100644 --- a/app/policies/profile_policy.rb +++ b/app/policies/profile_policy.rb @@ -1,14 +1,6 @@ # frozen_string_literal: true class ProfilePolicy < ApplicationPolicy - def show? - user - end - - def remove_avatar? - user - end - def update? user end diff --git a/app/policies/team_members/avatar_policy.rb b/app/policies/team_members/avatar_policy.rb index 77ec917b9d..a3d6486806 100644 --- a/app/policies/team_members/avatar_policy.rb +++ b/app/policies/team_members/avatar_policy.rb @@ -17,6 +17,6 @@ def authorize_current_user return false end - user_owner_role? || user_admin_role? + has_owner_or_admin_role? || record_belongs_to_user? end end diff --git a/app/policies/team_members/detail_policy.rb b/app/policies/team_members/detail_policy.rb index 7251b69303..a01f35cab8 100644 --- a/app/policies/team_members/detail_policy.rb +++ b/app/policies/team_members/detail_policy.rb @@ -21,6 +21,6 @@ def authorize_current_user return false end - user_owner_role? || user_admin_role? + has_owner_or_admin_role? || record_belongs_to_user? end end diff --git a/app/views/internal_api/v1/team_members/details/_detail.json.jbuilder b/app/views/internal_api/v1/team_members/details/_detail.json.jbuilder index 19c2ae45c5..dace39a008 100644 --- a/app/views/internal_api/v1/team_members/details/_detail.json.jbuilder +++ b/app/views/internal_api/v1/team_members/details/_detail.json.jbuilder @@ -1,5 +1,5 @@ # frozen_string_literal: true -json.extract! user, :first_name, :last_name, :date_of_birth, :phone, :personal_email_id, :social_accounts +json.extract! user, :id, :first_name, :last_name, :date_of_birth, :phone, :personal_email_id, :social_accounts json.date_format current_company.date_format json.avatar_url user.avatar_url diff --git a/config/routes/internal_api.rb b/config/routes/internal_api.rb index b7127daaff..cb714372d7 100644 --- a/config/routes/internal_api.rb +++ b/config/routes/internal_api.rb @@ -138,18 +138,12 @@ resources :providers, only: [:index, :update] end - resources :team, only: [:index, :destroy] do - resource :details, only: [:show, :update], controller: "team_members/details" - end - resources :users, concerns: :addressable do resources :previous_employments, only: [:create, :index, :show, :update], controller: "users/previous_employments" resources :devices, only: [:create, :index, :show, :update], controller: "users/devices" end - resource :profile, only: [:update, :show], controller: "profile" do - delete "/remove_avatar", to: "profile#remove_avatar" - end + resource :profile, only: [:update], controller: "profile" resources :vendors, only: [:create] resources :expense_categories, only: [:create] diff --git a/spec/requests/internal_api/v1/profile/remove_avatar_spec.rb b/spec/requests/internal_api/v1/profile/remove_avatar_spec.rb deleted file mode 100644 index e6dffd6e2c..0000000000 --- a/spec/requests/internal_api/v1/profile/remove_avatar_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -RSpec.describe "InternalApi::V1::Profile#remove_avatar", type: :request do - let(:user) { create(:user, :with_avatar) } - let(:company) { create(:company) } - - describe "remove avatar" do - before do - user.add_role :employee, company - sign_in user - send_request :delete, remove_avatar_internal_api_v1_profile_path, headers: auth_headers(user) - end - - it "removes user's avatar" do - expect(response).to have_http_status(:ok) - expect(user.reload.avatar.attached?).to be_falsey - end - end -end diff --git a/spec/requests/internal_api/v1/profile/show_spec.rb b/spec/requests/internal_api/v1/profile/show_spec.rb deleted file mode 100644 index 51ee64fa7a..0000000000 --- a/spec/requests/internal_api/v1/profile/show_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -RSpec.describe "InternalApi::V1::Profile#show", type: :request do - let(:user) { create(:user, :with_avatar) } - let(:company) { create(:company) } - - describe "index action" do - before do - user.add_role :employee, company - sign_in user - send_request :get, internal_api_v1_profile_path, headers: auth_headers(user) - end - - it "fetches user details & avatar" do - expect(response).to have_http_status(:ok) - expect(json_response["user"]["avatar_url"]).to eq(user.avatar_url) - expect(json_response["user"]["first_name"]).to eq(user.first_name) - expect(json_response["user"]["last_name"]).to eq(user.last_name) - end - end -end diff --git a/spec/requests/internal_api/v1/team_members/avatar/destroy_spec.rb b/spec/requests/internal_api/v1/team_members/avatar/destroy_spec.rb index 3c195efa6c..b406570893 100644 --- a/spec/requests/internal_api/v1/team_members/avatar/destroy_spec.rb +++ b/spec/requests/internal_api/v1/team_members/avatar/destroy_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.describe "InternalApi::V1::Profile#remove_avatar", type: :request do +RSpec.describe "InternalApi::V1::TeamMembers::AvatarController#destroy", type: :request do let(:company) { create(:company) } let(:company2) { create(:company) } let(:admin) { create(:user, current_workspace_id: company.id) } @@ -45,6 +45,21 @@ end end + context "when employee wants to remove own avatar" do + before do + create(:employment, user:, company:) + + user.add_role :employee, company + sign_in user + send_request :delete, internal_api_v1_team_avatar_path(user.id), headers: auth_headers(user) + end + + it "removes user's avatar" do + expect(response).to have_http_status(:ok) + expect(user.reload.avatar.attached?).to be_falsey + end + end + context "when logged in admin wants to remove avatar of employee from a different company" do before do create(:employment, user: admin, company:) @@ -76,4 +91,21 @@ expect(response).to have_http_status(:not_found) end end + + context "when logged in employee wants to remove avatar of another employee from a same company" do + before do + create(:employment, user:, company:) + create(:employment, user: user2, company:) + + user.add_role :employee, company + user2.add_role :employee, company + sign_in user + send_request :delete, internal_api_v1_team_avatar_path(user2.id), headers: auth_headers(user) + end + + it "is unsuccessful" do + expect(response).to have_http_status(:forbidden) + expect(json_response["errors"]).to eq("You are not authorized to perform this action.") + end + end end diff --git a/spec/requests/internal_api/v1/team_members/avatar/update_spec.rb b/spec/requests/internal_api/v1/team_members/avatar/update_spec.rb index 1ed20bb50e..a19a9e68a5 100644 --- a/spec/requests/internal_api/v1/team_members/avatar/update_spec.rb +++ b/spec/requests/internal_api/v1/team_members/avatar/update_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.describe "InternalApi::V1::TeamMembers::AvatarController::#update", type: :request do +RSpec.describe "InternalApi::V1::TeamMembers::AvatarController#update", type: :request do let(:company) { create(:company) } let(:company2) { create(:company) } let(:admin) { create(:user, current_workspace_id: company.id) } diff --git a/spec/requests/internal_api/v1/team_members/details/show_spec.rb b/spec/requests/internal_api/v1/team_members/details/show_spec.rb index a577b086a8..fe24bd08cd 100644 --- a/spec/requests/internal_api/v1/team_members/details/show_spec.rb +++ b/spec/requests/internal_api/v1/team_members/details/show_spec.rb @@ -2,11 +2,11 @@ require "rails_helper" -RSpec.describe "Details#show", type: :request do +RSpec.describe "InternalApi::V1::TeamMembers::DetailsController#show", type: :request do let(:company) { create(:company) } let(:company2) { create(:company) } - let(:user) { create(:user, current_workspace_id: company.id) } - let(:user2) { create(:user, current_workspace_id: company2.id) } + let(:user) { create(:user, :with_avatar, current_workspace_id: company.id) } + let(:user2) { create(:user, :with_avatar, current_workspace_id: company2.id) } let(:employment) { create(:employment, user:, company:) } context "when Owner wants to see details of employee of his company" do @@ -52,9 +52,15 @@ send_request :get, internal_api_v1_team_details_path(employment.user_id), headers: auth_headers(user) end - it "is unsuccessful" do - expect(response).to have_http_status(:forbidden) - expect(json_response["errors"]).to eq("You are not authorized to perform this action.") + it "is successful" do + expect(response).to have_http_status(:ok) + expect(json_response["avatar_url"]).to eq(JSON.parse(user.avatar_url.to_json)) + expect(json_response["first_name"]).to eq(JSON.parse(user.first_name.to_json)) + expect(json_response["last_name"]).to eq(JSON.parse(user.last_name.to_json)) + expect(json_response["personal_email_id"]).to eq(JSON.parse(user.personal_email_id.to_json)) + expect(json_response["date_of_birth"]).to eq(JSON.parse(user.date_of_birth.to_json)) + expect(json_response["phone"]).to eq(JSON.parse(user.phone.to_json)) + expect(json_response["social_accounts"]).to eq(JSON.parse(user.social_accounts.to_json)) end end diff --git a/spec/requests/internal_api/v1/team_members/details/update_spec.rb b/spec/requests/internal_api/v1/team_members/details/update_spec.rb index 96bc0d24f3..8bb24619de 100644 --- a/spec/requests/internal_api/v1/team_members/details/update_spec.rb +++ b/spec/requests/internal_api/v1/team_members/details/update_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.describe "InternalApi::V1::TeamMembers::DetailsController::#update", type: :request do +RSpec.describe "InternalApi::V1::TeamMembers::DetailsController#update", type: :request do let(:company) { create(:company) } let(:company2) { create(:company) } let(:user) { create(:user, current_workspace_id: company.id) } @@ -79,9 +79,14 @@ }), headers: auth_headers(user) end - it "is unsuccessful" do - expect(response).to have_http_status(:forbidden) - expect(json_response["errors"]).to eq("You are not authorized to perform this action.") + it "is successful" do + expect(response).to have_http_status(:ok) + expect(json_response["first_name"]).to eq(JSON.parse(@user_details["first_name"].to_json)) + expect(json_response["last_name"]).to eq(JSON.parse(@user_details["last_name"].to_json)) + expect(json_response["personal_email_id"]).to eq(JSON.parse(@user_details["personal_email_id"].to_json)) + expect(json_response["date_of_birth"]).to eq(JSON.parse(@user_details["date_of_birth"].to_json)) + expect(json_response["phone"]).to eq(JSON.parse(@user_details["phone"].to_json)) + expect(json_response["social_accounts"]).to eq(JSON.parse(@user_details["social_accounts"].to_json)) end end From 2b874c79e22d4fdacf66da6e685a09d8c9dc0ccd Mon Sep 17 00:00:00 2001 From: Apoorv Tiwari Date: Wed, 26 Jun 2024 10:49:48 +0530 Subject: [PATCH 2/3] Devices API (#1699) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add insurance details * api for bulk operations * add insurance details while adding device * fix-rubocop * add condition for insurance validation * fix spec and add service specs * refactor * Revert "refactor" This reverts commit 9d5bda3ac5ae991f692ecb2bf93c5857e899b523. * refactor code * use insurance activation * remove packages from schema --------- Co-authored-by: “Apoorv <“tiwari.apoorv1316@gmail.com”> --- .../v1/users/devices_controller.rb | 22 +- app/models/device.rb | 34 ++- app/services/bulk_devices_service.rb | 46 ++++ .../v1/users/devices/_device.json.jbuilder | 2 +- config/locales/en.yml | 5 +- ...144124_add_insurance_details_to_devices.rb | 9 + db/schema.rb | 4 +- spec/factories/devices.rb | 3 + spec/models/device_spec.rb | 6 + .../v1/users/devices/create_spec.rb | 36 +-- .../v1/users/devices/update_spec.rb | 251 ------------------ spec/services/bulk_devices_services_spec.rb | 154 +++++++++++ 12 files changed, 269 insertions(+), 303 deletions(-) create mode 100644 app/services/bulk_devices_service.rb create mode 100644 db/migrate/20240311144124_add_insurance_details_to_devices.rb delete mode 100644 spec/requests/internal_api/v1/users/devices/update_spec.rb create mode 100644 spec/services/bulk_devices_services_spec.rb diff --git a/app/controllers/internal_api/v1/users/devices_controller.rb b/app/controllers/internal_api/v1/users/devices_controller.rb index b60abdffc1..7b75741429 100644 --- a/app/controllers/internal_api/v1/users/devices_controller.rb +++ b/app/controllers/internal_api/v1/users/devices_controller.rb @@ -9,22 +9,15 @@ def index render :index, locals: { devices: }, status: :ok end - def create - authorize @user, policy_class: Users::DevicePolicy - device = @user.devices.new(device_params.merge(issued_to: @user, issued_by: @user.current_workspace)) - device.save! - render :create, locals: { device: }, status: :ok - end - def show authorize device, policy_class: Users::DevicePolicy render :show, locals: { device: }, status: :ok end - def update - authorize device, policy_class: Users::DevicePolicy - device.update!(device_params) - render :update, locals: { device: }, status: :ok + def create + authorize @user, policy_class: Users::DevicePolicy + BulkDevicesService.new(device_params, set_user).process + render json: { notice: I18n.t("devices.update.success") } end private @@ -38,8 +31,13 @@ def device end def device_params + shared_params = [:device_type, :name, :serial_number, :is_insured, :insurance_activation_date, + :insurance_expiry_date, specifications: [:processor, :ram, :graphics]] + params.require(:device).permit( - :device_type, :name, :serial_number, specifications: {} + add_devices: shared_params, + update_devices: [:id] + shared_params, + remove_devices: [] ) end end diff --git a/app/models/device.rb b/app/models/device.rb index 223e87d46c..be9a9a2d67 100644 --- a/app/models/device.rb +++ b/app/models/device.rb @@ -4,18 +4,18 @@ # # Table name: devices # -# id :bigint not null, primary key -# device_type :string default("laptop") -# insurance_bought_date :date -# insurance_expiry_date :date -# is_insured :boolean default(FALSE) -# name :string -# serial_number :string -# specifications :jsonb -# created_at :datetime not null -# updated_at :datetime not null -# company_id :bigint not null -# user_id :bigint not null +# id :bigint not null, primary key +# device_type :string default("laptop") +# insurance_activation_date :date +# insurance_expiry_date :date +# is_insured :boolean default(FALSE) +# name :string +# serial_number :string +# specifications :jsonb +# created_at :datetime not null +# updated_at :datetime not null +# company_id :bigint not null +# user_id :bigint not null # # Indexes # @@ -42,14 +42,22 @@ class Device < ApplicationRecord # Validations after_initialize :set_default_specifications, if: :new_record? validates :name, length: { maximum: 100 } + validates :insurance_activation_date, presence: true, if: :is_insured? + validates :insurance_expiry_date, presence: true, if: :is_insured? + validates :insurance_expiry_date, comparison: { greater_than_or_equal_to: :insurance_activation_date }, + if: :is_insured? private def set_default_specifications - self.specifications = { + self.specifications ||= { "processor": "", "ram": "", "graphics": "" } end + + def is_insured? + is_insured + end end diff --git a/app/services/bulk_devices_service.rb b/app/services/bulk_devices_service.rb new file mode 100644 index 0000000000..754cbb571b --- /dev/null +++ b/app/services/bulk_devices_service.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class BulkDevicesService + attr_reader :user, :device_params + + def initialize(device_params, user) + @device_params = device_params + @user = user + end + + def process + ActiveRecord::Base.transaction do + add_new_devices + update_devices + remove_devices + end + end + + private + + def add_new_devices + return if device_params[:add_devices].blank? + + device_params[:add_devices].each do |params| + new_device = user.devices.new(params) + new_device.issued_to = user + new_device.issued_by = user.current_workspace + new_device.save! + end + end + + def update_devices + return if device_params[:update_devices].blank? + + device_params[:update_devices].each do |params| + device = Device.find_by!(id: params[:id]) + device.update!(params.except(:id)) + end + end + + def remove_devices + return if device_params[:remove_devices].blank? + + user.devices.where(id: device_params[:remove_devices]).destroy_all + end +end diff --git a/app/views/internal_api/v1/users/devices/_device.json.jbuilder b/app/views/internal_api/v1/users/devices/_device.json.jbuilder index 83ec7c3e87..2de862af6f 100644 --- a/app/views/internal_api/v1/users/devices/_device.json.jbuilder +++ b/app/views/internal_api/v1/users/devices/_device.json.jbuilder @@ -1,3 +1,3 @@ # frozen_string_literal: true -json.extract! device, :device_type, :name, :serial_number, :specifications +json.extract! device, *device.attributes.keys.map(&:to_sym) diff --git a/config/locales/en.yml b/config/locales/en.yml index c7946d4f1d..9b7ad888fe 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -170,6 +170,9 @@ en: employment: update: success: Employment updated successfully + devices: + update: + success: Devices updated successfully registration: sign_up: sign up sign_in: sign in @@ -295,4 +298,4 @@ en: invalid: Invalid email or password expenses: update: "Expense updated successfully" - destroy: "Expense deleted successfully" \ No newline at end of file + destroy: "Expense deleted successfully" diff --git a/db/migrate/20240311144124_add_insurance_details_to_devices.rb b/db/migrate/20240311144124_add_insurance_details_to_devices.rb new file mode 100644 index 0000000000..494317c5c0 --- /dev/null +++ b/db/migrate/20240311144124_add_insurance_details_to_devices.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddInsuranceDetailsToDevices < ActiveRecord::Migration[7.0] + def change + add_column :devices, :is_insured, :boolean, default: false + add_column :devices, :insurance_activation_date, :date + add_column :devices, :insurance_expiry_date, :date + end +end diff --git a/db/schema.rb b/db/schema.rb index 4e7bd6c553..6db9c2c7cf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_05_16_054849) do +ActiveRecord::Schema[7.1].define(version: 2024_06_03_053942) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -194,7 +194,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.boolean "is_insured", default: false - t.date "insurance_bought_date" + t.date "insurance_activation_date" t.date "insurance_expiry_date" t.index ["company_id"], name: "index_devices_on_company_id" t.index ["device_type"], name: "index_devices_on_device_type" diff --git a/spec/factories/devices.rb b/spec/factories/devices.rb index 2a07d5d4c2..7699cc0fd5 100644 --- a/spec/factories/devices.rb +++ b/spec/factories/devices.rb @@ -6,5 +6,8 @@ issued_by factory: :company name { Faker::Alphanumeric.alphanumeric } serial_number { Faker::Alphanumeric.alphanumeric } + is_insured { true } + insurance_activation_date { Faker::Date.between(from: "2019-04-01", to: Date.today) } + insurance_expiry_date { Faker::Date.between(from: self.insurance_activation_date, to: Date.today) } end end diff --git a/spec/models/device_spec.rb b/spec/models/device_spec.rb index 4a3a70a773..5272036ce1 100644 --- a/spec/models/device_spec.rb +++ b/spec/models/device_spec.rb @@ -33,4 +33,10 @@ expect(device.device_type).to eq("laptop") end end + + describe "validate comparisons" do + it "insurance expiry date should not be after bought date" do + expect(device.insurance_expiry_date).to be >= device.insurance_activation_date + end + end end diff --git a/spec/requests/internal_api/v1/users/devices/create_spec.rb b/spec/requests/internal_api/v1/users/devices/create_spec.rb index dbd44f9ade..25eb420d78 100644 --- a/spec/requests/internal_api/v1/users/devices/create_spec.rb +++ b/spec/requests/internal_api/v1/users/devices/create_spec.rb @@ -24,15 +24,13 @@ before do send_request :post, internal_api_v1_user_devices_path( user_id: user.id, - device: device_details + device: { add_devices: [device_details] } ), headers: auth_headers(user) end it "is successful" do expect(response).to have_http_status(:ok) - expect(json_response["device_type"]).to eq("laptop") - expect(json_response["name"]).to eq(JSON.parse(device_details[:name].to_json)) - expect(json_response["serial_number"]).to eq(JSON.parse(device_details[:serial_number].to_json)) + expect(json_response["notice"]).to eq(I18n.t("devices.update.success")) end end @@ -41,15 +39,13 @@ create(:employment, company:, user: employee) send_request :post, internal_api_v1_user_devices_path( user_id: employee.id, - device: device_details + device: { add_devices: [device_details] } ), headers: auth_headers(user) end it "is successful" do expect(response).to have_http_status(:ok) - expect(json_response["device_type"]).to eq("laptop") - expect(json_response["name"]).to eq(JSON.parse(device_details[:name].to_json)) - expect(json_response["serial_number"]).to eq(JSON.parse(device_details[:serial_number].to_json)) + expect(json_response["notice"]).to eq(I18n.t("devices.update.success")) end end @@ -58,7 +54,7 @@ create(:employment, company: company2, user: user2) send_request :post, internal_api_v1_user_devices_path( user_id: user2.id, - device: device_details + device: { add_devices: [device_details] } ), headers: auth_headers(user) end @@ -72,7 +68,7 @@ create(:employment, company:, user: employee) send_request :post, internal_api_v1_user_devices_path( user_id: "abc", - device: device_details + device: { add_devices: [device_details] } ), headers: auth_headers(user) end @@ -92,15 +88,13 @@ before do send_request :post, internal_api_v1_user_devices_path( user_id: user.id, - device: device_details + device: { add_devices: [device_details] } ), headers: auth_headers(user) end it "is successful" do expect(response).to have_http_status(:ok) - expect(json_response["device_type"]).to eq("laptop") - expect(json_response["name"]).to eq(JSON.parse(device_details[:name].to_json)) - expect(json_response["serial_number"]).to eq(JSON.parse(device_details[:serial_number].to_json)) + expect(json_response["notice"]).to eq(I18n.t("devices.update.success")) end end @@ -109,15 +103,13 @@ create(:employment, company:, user: employee) send_request :post, internal_api_v1_user_devices_path( user_id: employee.id, - device: device_details + device: { add_devices: [device_details] } ), headers: auth_headers(user) end it "is successful" do expect(response).to have_http_status(:ok) - expect(json_response["device_type"]).to eq("laptop") - expect(json_response["name"]).to eq(JSON.parse(device_details[:name].to_json)) - expect(json_response["serial_number"]).to eq(JSON.parse(device_details[:serial_number].to_json)) + expect(json_response["notice"]).to eq(I18n.t("devices.update.success")) end end end @@ -132,15 +124,13 @@ before do send_request :post, internal_api_v1_user_devices_path( user_id: user.id, - device: device_details + device: { add_devices: [device_details] } ), headers: auth_headers(user) end it "is successful" do expect(response).to have_http_status(:ok) - expect(json_response["device_type"]).to eq("laptop") - expect(json_response["name"]).to eq(JSON.parse(device_details[:name].to_json)) - expect(json_response["serial_number"]).to eq(JSON.parse(device_details[:serial_number].to_json)) + expect(json_response["notice"]).to eq(I18n.t("devices.update.success")) end end @@ -149,7 +139,7 @@ create(:employment, company:, user: employee) send_request :post, internal_api_v1_user_devices_path( user_id: employee.id, - device: device_details + device: { add_devices: [device_details] } ), headers: auth_headers(user) end diff --git a/spec/requests/internal_api/v1/users/devices/update_spec.rb b/spec/requests/internal_api/v1/users/devices/update_spec.rb deleted file mode 100644 index 95dd1db951..0000000000 --- a/spec/requests/internal_api/v1/users/devices/update_spec.rb +++ /dev/null @@ -1,251 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -RSpec.describe "Devices#update", type: :request do - let(:company) { create(:company) } - let(:company2) { create(:company) } - let(:user) { create(:user, current_workspace_id: company.id) } - let(:employee) { create(:user, current_workspace_id: company.id) } - let!(:device_of_employee) { create(:device, user_id: employee.id, company_id: company.id) } - let!(:device_of_user) { create(:device, user_id: user.id, company_id: company.id) } - let!(:updated_device_details) { attributes_for(:device) } - - before do - create(:employment, company:, user:) - end - - context "when logged in user is Owner" do - before do - user.add_role :owner, company - sign_in user - end - - context "when user wants to update his own details" do - before do - send_request :patch, internal_api_v1_user_device_path( - user_id: user.id, - id: device_of_user.id, - params: { - device: updated_device_details - }), headers: auth_headers(user) - end - - it "is successful" do - device_of_user.reload - expect(response).to have_http_status(:ok) - expect(json_response["device_type"]).to eq("laptop") - expect(json_response["name"]).to eq(JSON.parse(updated_device_details[:name].to_json)) - expect(json_response["serial_number"]).to eq(JSON.parse(updated_device_details[:serial_number].to_json)) - end - end - - context "when user wants to update details of an employee of his own workspace" do - before do - create(:employment, company:, user: employee) - send_request :patch, internal_api_v1_user_device_path( - user_id: employee.id, - id: device_of_employee.id, - params: { - device: updated_device_details - }), headers: auth_headers(user) - end - - it "is successful" do - device_of_employee.reload - expect(response).to have_http_status(:ok) - expect(json_response["device_type"]).to eq("laptop") - expect(json_response["name"]).to eq(JSON.parse(updated_device_details[:name].to_json)) - expect(json_response["serial_number"]).to eq(JSON.parse(updated_device_details[:serial_number].to_json)) - end - end - - context "when user wants to update details of an employee of a different workspace" do - before do - create(:employment, company: company2, user: employee) - send_request :patch, internal_api_v1_user_device_path( - user_id: employee.id, - id: device_of_employee.id, - params: { - device: updated_device_details - }), headers: auth_headers(user) - end - - it "is forbidden" do - device_of_employee.reload - expect(response).to have_http_status(:forbidden) - end - end - - context "when user wants to update details of an employee id that does not exist" do - before do - send_request :patch, internal_api_v1_user_device_path( - user_id: "abc", - id: device_of_employee.id, - params: { - device: updated_device_details - }), headers: auth_headers(user) - end - - it "is not found" do - device_of_employee.reload - expect(response).to have_http_status(:not_found) - end - end - end - - context "when logged in user is Admin" do - before do - user.add_role :admin, company - sign_in user - end - - context "when user wants to update his own details" do - before do - send_request :patch, internal_api_v1_user_device_path( - user_id: user.id, - id: device_of_user.id, - params: { - device: updated_device_details - }), headers: auth_headers(user) - end - - it "is successful" do - device_of_user.reload - expect(response).to have_http_status(:ok) - expect(json_response["device_type"]).to eq("laptop") - expect(json_response["name"]).to eq(JSON.parse(updated_device_details[:name].to_json)) - expect(json_response["serial_number"]).to eq(JSON.parse(updated_device_details[:serial_number].to_json)) - end - end - - context "when user wants to update details of an employee of his own workspace" do - before do - create(:employment, company:, user: employee) - send_request :patch, internal_api_v1_user_device_path( - user_id: employee.id, - id: device_of_employee.id, - params: { - device: updated_device_details - }), headers: auth_headers(user) - end - - it "is successful" do - device_of_employee.reload - expect(response).to have_http_status(:ok) - expect(json_response["device_type"]).to eq("laptop") - expect(json_response["name"]).to eq(JSON.parse(updated_device_details[:name].to_json)) - expect(json_response["serial_number"]).to eq(JSON.parse(updated_device_details[:serial_number].to_json)) - end - end - - context "when user wants to update details of an employee of a different workspace" do - before do - create(:employment, company: company2, user: employee) - send_request :patch, internal_api_v1_user_device_path( - user_id: employee.id, - id: device_of_employee.id, - params: { - device: updated_device_details - }), headers: auth_headers(user) - end - - it "is forbidden" do - device_of_employee.reload - expect(response).to have_http_status(:forbidden) - end - end - - context "when user wants to update details of an employee id that does not exist" do - before do - send_request :patch, internal_api_v1_user_device_path( - user_id: "abc", - id: device_of_employee.id, - params: { - device: updated_device_details - }), headers: auth_headers(user) - end - - it "is not found" do - device_of_employee.reload - expect(response).to have_http_status(:not_found) - end - end - end - - context "when logged in user is Employee" do - before do - user.add_role :employee, company - sign_in user - end - - context "when user wants to update his own details" do - before do - send_request :patch, internal_api_v1_user_device_path( - user_id: user.id, - id: device_of_user.id, - params: { - device: updated_device_details - }), headers: auth_headers(user) - end - - it "is successful" do - device_of_user.reload - expect(response).to have_http_status(:ok) - expect(json_response["device_type"]).to eq("laptop") - expect(json_response["name"]).to eq(JSON.parse(updated_device_details[:name].to_json)) - expect(json_response["serial_number"]).to eq(JSON.parse(updated_device_details[:serial_number].to_json)) - end - end - - context "when user wants to update details of an employee of his own workspace" do - before do - create(:employment, company:, user: employee) - send_request :patch, internal_api_v1_user_device_path( - user_id: employee.id, - id: device_of_employee.id, - params: { - device: updated_device_details - }), headers: auth_headers(user) - end - - it "is forbidden" do - device_of_employee.reload - expect(response).to have_http_status(:forbidden) - end - end - - context "when user wants to update details of an employee of a different workspace" do - before do - create(:employment, company: company2, user: employee) - send_request :patch, internal_api_v1_user_device_path( - user_id: employee.id, - id: device_of_employee.id, - params: { - device: updated_device_details - }), headers: auth_headers(user) - end - - it "is forbidden" do - device_of_employee.reload - expect(response).to have_http_status(:forbidden) - end - end - - context "when user wants to update details of an employee id that does not exist" do - before do - send_request :patch, internal_api_v1_user_device_path( - user_id: "abc", - id: device_of_employee.id, - params: { - device: updated_device_details - }), headers: auth_headers(user) - end - - it "is not found" do - device_of_employee.reload - expect(response).to have_http_status(:not_found) - end - end - end -end diff --git a/spec/services/bulk_devices_services_spec.rb b/spec/services/bulk_devices_services_spec.rb new file mode 100644 index 0000000000..4cbfec970a --- /dev/null +++ b/spec/services/bulk_devices_services_spec.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe BulkDevicesService do + let(:company) { create(:company) } + let(:user) { create(:user, current_workspace_id: company.id) } + let!(:device1) { Device.create( + issued_to: user, issued_by: company, device_type: "laptop", + name: "Device 1", serial_number: "123456", specifications: { processor: "i5", ram: "8GB", graphics: "Intel" }, + is_insured: true, insurance_activation_date: "2020-01-01", insurance_expiry_date: "2023-01-01") +} + let!(:device2) { Device.create( + issued_to: user, issued_by: company, device_type: "mobile", + name: "Device 2", serial_number: "654321", specifications: { processor: "i3", ram: "4GB", graphics: "AMD" }, + is_insured: false) +} + + describe "#process" do + context "when adding new devices" do + let(:new_device_params) do + { + add_devices: [ + { + device_type: "laptop", + name: "New Laptop", + serial_number: "123ABC", + is_insured: true, + insurance_activation_date: "2023-01-01", + insurance_expiry_date: "2024-01-01", + specifications: { processor: "i7", ram: "16GB", graphics: "NVIDIA" } + } + ] + } + end + + it "adds new devices to the user" do + expect { + described_class.new(new_device_params, user).process + }.to change { user.devices.count }.by(1) + end + + it "sets the correct attributes for the new devices" do + described_class.new(new_device_params, user).process + new_device = user.devices.last + + expect(new_device.device_type).to eq("laptop") + expect(new_device.name).to eq("New Laptop") + expect(new_device.serial_number).to eq("123ABC") + expect(new_device.is_insured).to be true + expect(new_device.insurance_activation_date).to eq(Date.parse("2023-01-01")) + expect(new_device.insurance_expiry_date).to eq(Date.parse("2024-01-01")) + expect(new_device.specifications).to eq({ "processor" => "i7", "ram" => "16GB", "graphics" => "NVIDIA" }) + end + end + + context "when updating existing devices" do + let(:update_device_params) do + { + update_devices: [ + { + id: device1.id, + device_type: "mobile", + name: "Updated mobile", + serial_number: "456DEF", + is_insured: false, + specifications: { processor: "i5", ram: "8GB", graphics: "Integrated" } + } + ] + } + end + + it "updates the specified devices" do + described_class.new(update_device_params, user).process + device1.reload + + expect(device1.device_type).to eq("mobile") + expect(device1.name).to eq("Updated mobile") + expect(device1.serial_number).to eq("456DEF") + expect(device1.is_insured).to be false + expect(device1.specifications).to eq({ "processor" => "i5", "ram" => "8GB", "graphics" => "Integrated" }) + end + end + + context "when removing devices" do + let(:remove_device_params) do + { + remove_devices: [device2.id] + } + end + + it "removes the specified devices" do + expect { + described_class.new(remove_device_params, user).process + }.to change { user.devices.count }.by(-1) + end + end + + context "when performing multiple actions" do + let(:multiple_actions_params) do + { + add_devices: [ + { + device_type: "laptop", + name: "New Laptop", + serial_number: "123ABC", + is_insured: true, + insurance_activation_date: "2023-01-01", + insurance_expiry_date: "2024-01-01", + specifications: { processor: "i7", ram: "16GB", graphics: "NVIDIA" } + } + ], + update_devices: [ + { + id: device1.id, + device_type: "mobile", + name: "Updated mobile", + serial_number: "456DEF", + is_insured: false, + specifications: { processor: "i5", ram: "8GB", graphics: "Integrated" } + } + ], + remove_devices: [device2.id] + } + end + + it "adds new devices" do + expect { + described_class.new(multiple_actions_params, user).process + }.to change { user.devices.count }.by(0) + + new_device = user.devices.find_by(serial_number: "123ABC") + expect(new_device).not_to be_nil + end + + it "updates existing devices" do + described_class.new(multiple_actions_params, user).process + device1.reload + + expect(device1.device_type).to eq("mobile") + expect(device1.name).to eq("Updated mobile") + expect(device1.serial_number).to eq("456DEF") + expect(device1.is_insured).to be false + expect(device1.specifications).to eq({ "processor" => "i5", "ram" => "8GB", "graphics" => "Integrated" }) + end + + it "removes specified devices" do + described_class.new(multiple_actions_params, user).process + + expect(user.devices.exists?(id: device2.id)).to be false + end + end + end +end From ae4babf1103a619dc094d804a92f55710a59b3f0 Mon Sep 17 00:00:00 2001 From: apoorv1316 Date: Wed, 26 Jun 2024 11:12:47 +0530 Subject: [PATCH 3/3] Revert "Devices API (#1699)" This reverts commit 2b874c79e22d4fdacf66da6e685a09d8c9dc0ccd. --- .../v1/users/devices_controller.rb | 22 +- app/models/device.rb | 34 +-- app/services/bulk_devices_service.rb | 46 ---- .../v1/users/devices/_device.json.jbuilder | 2 +- config/locales/en.yml | 5 +- ...144124_add_insurance_details_to_devices.rb | 9 - db/schema.rb | 4 +- spec/factories/devices.rb | 3 - spec/models/device_spec.rb | 6 - .../v1/users/devices/create_spec.rb | 36 ++- .../v1/users/devices/update_spec.rb | 251 ++++++++++++++++++ spec/services/bulk_devices_services_spec.rb | 154 ----------- 12 files changed, 303 insertions(+), 269 deletions(-) delete mode 100644 app/services/bulk_devices_service.rb delete mode 100644 db/migrate/20240311144124_add_insurance_details_to_devices.rb create mode 100644 spec/requests/internal_api/v1/users/devices/update_spec.rb delete mode 100644 spec/services/bulk_devices_services_spec.rb diff --git a/app/controllers/internal_api/v1/users/devices_controller.rb b/app/controllers/internal_api/v1/users/devices_controller.rb index 7b75741429..b60abdffc1 100644 --- a/app/controllers/internal_api/v1/users/devices_controller.rb +++ b/app/controllers/internal_api/v1/users/devices_controller.rb @@ -9,15 +9,22 @@ def index render :index, locals: { devices: }, status: :ok end + def create + authorize @user, policy_class: Users::DevicePolicy + device = @user.devices.new(device_params.merge(issued_to: @user, issued_by: @user.current_workspace)) + device.save! + render :create, locals: { device: }, status: :ok + end + def show authorize device, policy_class: Users::DevicePolicy render :show, locals: { device: }, status: :ok end - def create - authorize @user, policy_class: Users::DevicePolicy - BulkDevicesService.new(device_params, set_user).process - render json: { notice: I18n.t("devices.update.success") } + def update + authorize device, policy_class: Users::DevicePolicy + device.update!(device_params) + render :update, locals: { device: }, status: :ok end private @@ -31,13 +38,8 @@ def device end def device_params - shared_params = [:device_type, :name, :serial_number, :is_insured, :insurance_activation_date, - :insurance_expiry_date, specifications: [:processor, :ram, :graphics]] - params.require(:device).permit( - add_devices: shared_params, - update_devices: [:id] + shared_params, - remove_devices: [] + :device_type, :name, :serial_number, specifications: {} ) end end diff --git a/app/models/device.rb b/app/models/device.rb index be9a9a2d67..223e87d46c 100644 --- a/app/models/device.rb +++ b/app/models/device.rb @@ -4,18 +4,18 @@ # # Table name: devices # -# id :bigint not null, primary key -# device_type :string default("laptop") -# insurance_activation_date :date -# insurance_expiry_date :date -# is_insured :boolean default(FALSE) -# name :string -# serial_number :string -# specifications :jsonb -# created_at :datetime not null -# updated_at :datetime not null -# company_id :bigint not null -# user_id :bigint not null +# id :bigint not null, primary key +# device_type :string default("laptop") +# insurance_bought_date :date +# insurance_expiry_date :date +# is_insured :boolean default(FALSE) +# name :string +# serial_number :string +# specifications :jsonb +# created_at :datetime not null +# updated_at :datetime not null +# company_id :bigint not null +# user_id :bigint not null # # Indexes # @@ -42,22 +42,14 @@ class Device < ApplicationRecord # Validations after_initialize :set_default_specifications, if: :new_record? validates :name, length: { maximum: 100 } - validates :insurance_activation_date, presence: true, if: :is_insured? - validates :insurance_expiry_date, presence: true, if: :is_insured? - validates :insurance_expiry_date, comparison: { greater_than_or_equal_to: :insurance_activation_date }, - if: :is_insured? private def set_default_specifications - self.specifications ||= { + self.specifications = { "processor": "", "ram": "", "graphics": "" } end - - def is_insured? - is_insured - end end diff --git a/app/services/bulk_devices_service.rb b/app/services/bulk_devices_service.rb deleted file mode 100644 index 754cbb571b..0000000000 --- a/app/services/bulk_devices_service.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -class BulkDevicesService - attr_reader :user, :device_params - - def initialize(device_params, user) - @device_params = device_params - @user = user - end - - def process - ActiveRecord::Base.transaction do - add_new_devices - update_devices - remove_devices - end - end - - private - - def add_new_devices - return if device_params[:add_devices].blank? - - device_params[:add_devices].each do |params| - new_device = user.devices.new(params) - new_device.issued_to = user - new_device.issued_by = user.current_workspace - new_device.save! - end - end - - def update_devices - return if device_params[:update_devices].blank? - - device_params[:update_devices].each do |params| - device = Device.find_by!(id: params[:id]) - device.update!(params.except(:id)) - end - end - - def remove_devices - return if device_params[:remove_devices].blank? - - user.devices.where(id: device_params[:remove_devices]).destroy_all - end -end diff --git a/app/views/internal_api/v1/users/devices/_device.json.jbuilder b/app/views/internal_api/v1/users/devices/_device.json.jbuilder index 2de862af6f..83ec7c3e87 100644 --- a/app/views/internal_api/v1/users/devices/_device.json.jbuilder +++ b/app/views/internal_api/v1/users/devices/_device.json.jbuilder @@ -1,3 +1,3 @@ # frozen_string_literal: true -json.extract! device, *device.attributes.keys.map(&:to_sym) +json.extract! device, :device_type, :name, :serial_number, :specifications diff --git a/config/locales/en.yml b/config/locales/en.yml index 9b7ad888fe..c7946d4f1d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -170,9 +170,6 @@ en: employment: update: success: Employment updated successfully - devices: - update: - success: Devices updated successfully registration: sign_up: sign up sign_in: sign in @@ -298,4 +295,4 @@ en: invalid: Invalid email or password expenses: update: "Expense updated successfully" - destroy: "Expense deleted successfully" + destroy: "Expense deleted successfully" \ No newline at end of file diff --git a/db/migrate/20240311144124_add_insurance_details_to_devices.rb b/db/migrate/20240311144124_add_insurance_details_to_devices.rb deleted file mode 100644 index 494317c5c0..0000000000 --- a/db/migrate/20240311144124_add_insurance_details_to_devices.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -class AddInsuranceDetailsToDevices < ActiveRecord::Migration[7.0] - def change - add_column :devices, :is_insured, :boolean, default: false - add_column :devices, :insurance_activation_date, :date - add_column :devices, :insurance_expiry_date, :date - end -end diff --git a/db/schema.rb b/db/schema.rb index 6db9c2c7cf..4e7bd6c553 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_06_03_053942) do +ActiveRecord::Schema[7.1].define(version: 2024_05_16_054849) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -194,7 +194,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.boolean "is_insured", default: false - t.date "insurance_activation_date" + t.date "insurance_bought_date" t.date "insurance_expiry_date" t.index ["company_id"], name: "index_devices_on_company_id" t.index ["device_type"], name: "index_devices_on_device_type" diff --git a/spec/factories/devices.rb b/spec/factories/devices.rb index 7699cc0fd5..2a07d5d4c2 100644 --- a/spec/factories/devices.rb +++ b/spec/factories/devices.rb @@ -6,8 +6,5 @@ issued_by factory: :company name { Faker::Alphanumeric.alphanumeric } serial_number { Faker::Alphanumeric.alphanumeric } - is_insured { true } - insurance_activation_date { Faker::Date.between(from: "2019-04-01", to: Date.today) } - insurance_expiry_date { Faker::Date.between(from: self.insurance_activation_date, to: Date.today) } end end diff --git a/spec/models/device_spec.rb b/spec/models/device_spec.rb index 5272036ce1..4a3a70a773 100644 --- a/spec/models/device_spec.rb +++ b/spec/models/device_spec.rb @@ -33,10 +33,4 @@ expect(device.device_type).to eq("laptop") end end - - describe "validate comparisons" do - it "insurance expiry date should not be after bought date" do - expect(device.insurance_expiry_date).to be >= device.insurance_activation_date - end - end end diff --git a/spec/requests/internal_api/v1/users/devices/create_spec.rb b/spec/requests/internal_api/v1/users/devices/create_spec.rb index 25eb420d78..dbd44f9ade 100644 --- a/spec/requests/internal_api/v1/users/devices/create_spec.rb +++ b/spec/requests/internal_api/v1/users/devices/create_spec.rb @@ -24,13 +24,15 @@ before do send_request :post, internal_api_v1_user_devices_path( user_id: user.id, - device: { add_devices: [device_details] } + device: device_details ), headers: auth_headers(user) end it "is successful" do expect(response).to have_http_status(:ok) - expect(json_response["notice"]).to eq(I18n.t("devices.update.success")) + expect(json_response["device_type"]).to eq("laptop") + expect(json_response["name"]).to eq(JSON.parse(device_details[:name].to_json)) + expect(json_response["serial_number"]).to eq(JSON.parse(device_details[:serial_number].to_json)) end end @@ -39,13 +41,15 @@ create(:employment, company:, user: employee) send_request :post, internal_api_v1_user_devices_path( user_id: employee.id, - device: { add_devices: [device_details] } + device: device_details ), headers: auth_headers(user) end it "is successful" do expect(response).to have_http_status(:ok) - expect(json_response["notice"]).to eq(I18n.t("devices.update.success")) + expect(json_response["device_type"]).to eq("laptop") + expect(json_response["name"]).to eq(JSON.parse(device_details[:name].to_json)) + expect(json_response["serial_number"]).to eq(JSON.parse(device_details[:serial_number].to_json)) end end @@ -54,7 +58,7 @@ create(:employment, company: company2, user: user2) send_request :post, internal_api_v1_user_devices_path( user_id: user2.id, - device: { add_devices: [device_details] } + device: device_details ), headers: auth_headers(user) end @@ -68,7 +72,7 @@ create(:employment, company:, user: employee) send_request :post, internal_api_v1_user_devices_path( user_id: "abc", - device: { add_devices: [device_details] } + device: device_details ), headers: auth_headers(user) end @@ -88,13 +92,15 @@ before do send_request :post, internal_api_v1_user_devices_path( user_id: user.id, - device: { add_devices: [device_details] } + device: device_details ), headers: auth_headers(user) end it "is successful" do expect(response).to have_http_status(:ok) - expect(json_response["notice"]).to eq(I18n.t("devices.update.success")) + expect(json_response["device_type"]).to eq("laptop") + expect(json_response["name"]).to eq(JSON.parse(device_details[:name].to_json)) + expect(json_response["serial_number"]).to eq(JSON.parse(device_details[:serial_number].to_json)) end end @@ -103,13 +109,15 @@ create(:employment, company:, user: employee) send_request :post, internal_api_v1_user_devices_path( user_id: employee.id, - device: { add_devices: [device_details] } + device: device_details ), headers: auth_headers(user) end it "is successful" do expect(response).to have_http_status(:ok) - expect(json_response["notice"]).to eq(I18n.t("devices.update.success")) + expect(json_response["device_type"]).to eq("laptop") + expect(json_response["name"]).to eq(JSON.parse(device_details[:name].to_json)) + expect(json_response["serial_number"]).to eq(JSON.parse(device_details[:serial_number].to_json)) end end end @@ -124,13 +132,15 @@ before do send_request :post, internal_api_v1_user_devices_path( user_id: user.id, - device: { add_devices: [device_details] } + device: device_details ), headers: auth_headers(user) end it "is successful" do expect(response).to have_http_status(:ok) - expect(json_response["notice"]).to eq(I18n.t("devices.update.success")) + expect(json_response["device_type"]).to eq("laptop") + expect(json_response["name"]).to eq(JSON.parse(device_details[:name].to_json)) + expect(json_response["serial_number"]).to eq(JSON.parse(device_details[:serial_number].to_json)) end end @@ -139,7 +149,7 @@ create(:employment, company:, user: employee) send_request :post, internal_api_v1_user_devices_path( user_id: employee.id, - device: { add_devices: [device_details] } + device: device_details ), headers: auth_headers(user) end diff --git a/spec/requests/internal_api/v1/users/devices/update_spec.rb b/spec/requests/internal_api/v1/users/devices/update_spec.rb new file mode 100644 index 0000000000..95dd1db951 --- /dev/null +++ b/spec/requests/internal_api/v1/users/devices/update_spec.rb @@ -0,0 +1,251 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "Devices#update", type: :request do + let(:company) { create(:company) } + let(:company2) { create(:company) } + let(:user) { create(:user, current_workspace_id: company.id) } + let(:employee) { create(:user, current_workspace_id: company.id) } + let!(:device_of_employee) { create(:device, user_id: employee.id, company_id: company.id) } + let!(:device_of_user) { create(:device, user_id: user.id, company_id: company.id) } + let!(:updated_device_details) { attributes_for(:device) } + + before do + create(:employment, company:, user:) + end + + context "when logged in user is Owner" do + before do + user.add_role :owner, company + sign_in user + end + + context "when user wants to update his own details" do + before do + send_request :patch, internal_api_v1_user_device_path( + user_id: user.id, + id: device_of_user.id, + params: { + device: updated_device_details + }), headers: auth_headers(user) + end + + it "is successful" do + device_of_user.reload + expect(response).to have_http_status(:ok) + expect(json_response["device_type"]).to eq("laptop") + expect(json_response["name"]).to eq(JSON.parse(updated_device_details[:name].to_json)) + expect(json_response["serial_number"]).to eq(JSON.parse(updated_device_details[:serial_number].to_json)) + end + end + + context "when user wants to update details of an employee of his own workspace" do + before do + create(:employment, company:, user: employee) + send_request :patch, internal_api_v1_user_device_path( + user_id: employee.id, + id: device_of_employee.id, + params: { + device: updated_device_details + }), headers: auth_headers(user) + end + + it "is successful" do + device_of_employee.reload + expect(response).to have_http_status(:ok) + expect(json_response["device_type"]).to eq("laptop") + expect(json_response["name"]).to eq(JSON.parse(updated_device_details[:name].to_json)) + expect(json_response["serial_number"]).to eq(JSON.parse(updated_device_details[:serial_number].to_json)) + end + end + + context "when user wants to update details of an employee of a different workspace" do + before do + create(:employment, company: company2, user: employee) + send_request :patch, internal_api_v1_user_device_path( + user_id: employee.id, + id: device_of_employee.id, + params: { + device: updated_device_details + }), headers: auth_headers(user) + end + + it "is forbidden" do + device_of_employee.reload + expect(response).to have_http_status(:forbidden) + end + end + + context "when user wants to update details of an employee id that does not exist" do + before do + send_request :patch, internal_api_v1_user_device_path( + user_id: "abc", + id: device_of_employee.id, + params: { + device: updated_device_details + }), headers: auth_headers(user) + end + + it "is not found" do + device_of_employee.reload + expect(response).to have_http_status(:not_found) + end + end + end + + context "when logged in user is Admin" do + before do + user.add_role :admin, company + sign_in user + end + + context "when user wants to update his own details" do + before do + send_request :patch, internal_api_v1_user_device_path( + user_id: user.id, + id: device_of_user.id, + params: { + device: updated_device_details + }), headers: auth_headers(user) + end + + it "is successful" do + device_of_user.reload + expect(response).to have_http_status(:ok) + expect(json_response["device_type"]).to eq("laptop") + expect(json_response["name"]).to eq(JSON.parse(updated_device_details[:name].to_json)) + expect(json_response["serial_number"]).to eq(JSON.parse(updated_device_details[:serial_number].to_json)) + end + end + + context "when user wants to update details of an employee of his own workspace" do + before do + create(:employment, company:, user: employee) + send_request :patch, internal_api_v1_user_device_path( + user_id: employee.id, + id: device_of_employee.id, + params: { + device: updated_device_details + }), headers: auth_headers(user) + end + + it "is successful" do + device_of_employee.reload + expect(response).to have_http_status(:ok) + expect(json_response["device_type"]).to eq("laptop") + expect(json_response["name"]).to eq(JSON.parse(updated_device_details[:name].to_json)) + expect(json_response["serial_number"]).to eq(JSON.parse(updated_device_details[:serial_number].to_json)) + end + end + + context "when user wants to update details of an employee of a different workspace" do + before do + create(:employment, company: company2, user: employee) + send_request :patch, internal_api_v1_user_device_path( + user_id: employee.id, + id: device_of_employee.id, + params: { + device: updated_device_details + }), headers: auth_headers(user) + end + + it "is forbidden" do + device_of_employee.reload + expect(response).to have_http_status(:forbidden) + end + end + + context "when user wants to update details of an employee id that does not exist" do + before do + send_request :patch, internal_api_v1_user_device_path( + user_id: "abc", + id: device_of_employee.id, + params: { + device: updated_device_details + }), headers: auth_headers(user) + end + + it "is not found" do + device_of_employee.reload + expect(response).to have_http_status(:not_found) + end + end + end + + context "when logged in user is Employee" do + before do + user.add_role :employee, company + sign_in user + end + + context "when user wants to update his own details" do + before do + send_request :patch, internal_api_v1_user_device_path( + user_id: user.id, + id: device_of_user.id, + params: { + device: updated_device_details + }), headers: auth_headers(user) + end + + it "is successful" do + device_of_user.reload + expect(response).to have_http_status(:ok) + expect(json_response["device_type"]).to eq("laptop") + expect(json_response["name"]).to eq(JSON.parse(updated_device_details[:name].to_json)) + expect(json_response["serial_number"]).to eq(JSON.parse(updated_device_details[:serial_number].to_json)) + end + end + + context "when user wants to update details of an employee of his own workspace" do + before do + create(:employment, company:, user: employee) + send_request :patch, internal_api_v1_user_device_path( + user_id: employee.id, + id: device_of_employee.id, + params: { + device: updated_device_details + }), headers: auth_headers(user) + end + + it "is forbidden" do + device_of_employee.reload + expect(response).to have_http_status(:forbidden) + end + end + + context "when user wants to update details of an employee of a different workspace" do + before do + create(:employment, company: company2, user: employee) + send_request :patch, internal_api_v1_user_device_path( + user_id: employee.id, + id: device_of_employee.id, + params: { + device: updated_device_details + }), headers: auth_headers(user) + end + + it "is forbidden" do + device_of_employee.reload + expect(response).to have_http_status(:forbidden) + end + end + + context "when user wants to update details of an employee id that does not exist" do + before do + send_request :patch, internal_api_v1_user_device_path( + user_id: "abc", + id: device_of_employee.id, + params: { + device: updated_device_details + }), headers: auth_headers(user) + end + + it "is not found" do + device_of_employee.reload + expect(response).to have_http_status(:not_found) + end + end + end +end diff --git a/spec/services/bulk_devices_services_spec.rb b/spec/services/bulk_devices_services_spec.rb deleted file mode 100644 index 4cbfec970a..0000000000 --- a/spec/services/bulk_devices_services_spec.rb +++ /dev/null @@ -1,154 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -RSpec.describe BulkDevicesService do - let(:company) { create(:company) } - let(:user) { create(:user, current_workspace_id: company.id) } - let!(:device1) { Device.create( - issued_to: user, issued_by: company, device_type: "laptop", - name: "Device 1", serial_number: "123456", specifications: { processor: "i5", ram: "8GB", graphics: "Intel" }, - is_insured: true, insurance_activation_date: "2020-01-01", insurance_expiry_date: "2023-01-01") -} - let!(:device2) { Device.create( - issued_to: user, issued_by: company, device_type: "mobile", - name: "Device 2", serial_number: "654321", specifications: { processor: "i3", ram: "4GB", graphics: "AMD" }, - is_insured: false) -} - - describe "#process" do - context "when adding new devices" do - let(:new_device_params) do - { - add_devices: [ - { - device_type: "laptop", - name: "New Laptop", - serial_number: "123ABC", - is_insured: true, - insurance_activation_date: "2023-01-01", - insurance_expiry_date: "2024-01-01", - specifications: { processor: "i7", ram: "16GB", graphics: "NVIDIA" } - } - ] - } - end - - it "adds new devices to the user" do - expect { - described_class.new(new_device_params, user).process - }.to change { user.devices.count }.by(1) - end - - it "sets the correct attributes for the new devices" do - described_class.new(new_device_params, user).process - new_device = user.devices.last - - expect(new_device.device_type).to eq("laptop") - expect(new_device.name).to eq("New Laptop") - expect(new_device.serial_number).to eq("123ABC") - expect(new_device.is_insured).to be true - expect(new_device.insurance_activation_date).to eq(Date.parse("2023-01-01")) - expect(new_device.insurance_expiry_date).to eq(Date.parse("2024-01-01")) - expect(new_device.specifications).to eq({ "processor" => "i7", "ram" => "16GB", "graphics" => "NVIDIA" }) - end - end - - context "when updating existing devices" do - let(:update_device_params) do - { - update_devices: [ - { - id: device1.id, - device_type: "mobile", - name: "Updated mobile", - serial_number: "456DEF", - is_insured: false, - specifications: { processor: "i5", ram: "8GB", graphics: "Integrated" } - } - ] - } - end - - it "updates the specified devices" do - described_class.new(update_device_params, user).process - device1.reload - - expect(device1.device_type).to eq("mobile") - expect(device1.name).to eq("Updated mobile") - expect(device1.serial_number).to eq("456DEF") - expect(device1.is_insured).to be false - expect(device1.specifications).to eq({ "processor" => "i5", "ram" => "8GB", "graphics" => "Integrated" }) - end - end - - context "when removing devices" do - let(:remove_device_params) do - { - remove_devices: [device2.id] - } - end - - it "removes the specified devices" do - expect { - described_class.new(remove_device_params, user).process - }.to change { user.devices.count }.by(-1) - end - end - - context "when performing multiple actions" do - let(:multiple_actions_params) do - { - add_devices: [ - { - device_type: "laptop", - name: "New Laptop", - serial_number: "123ABC", - is_insured: true, - insurance_activation_date: "2023-01-01", - insurance_expiry_date: "2024-01-01", - specifications: { processor: "i7", ram: "16GB", graphics: "NVIDIA" } - } - ], - update_devices: [ - { - id: device1.id, - device_type: "mobile", - name: "Updated mobile", - serial_number: "456DEF", - is_insured: false, - specifications: { processor: "i5", ram: "8GB", graphics: "Integrated" } - } - ], - remove_devices: [device2.id] - } - end - - it "adds new devices" do - expect { - described_class.new(multiple_actions_params, user).process - }.to change { user.devices.count }.by(0) - - new_device = user.devices.find_by(serial_number: "123ABC") - expect(new_device).not_to be_nil - end - - it "updates existing devices" do - described_class.new(multiple_actions_params, user).process - device1.reload - - expect(device1.device_type).to eq("mobile") - expect(device1.name).to eq("Updated mobile") - expect(device1.serial_number).to eq("456DEF") - expect(device1.is_insured).to be false - expect(device1.specifications).to eq({ "processor" => "i5", "ram" => "8GB", "graphics" => "Integrated" }) - end - - it "removes specified devices" do - described_class.new(multiple_actions_params, user).process - - expect(user.devices.exists?(id: device2.id)).to be false - end - end - end -end