From 53d55c1d3dbbdc06c4111144e73433760d09726d Mon Sep 17 00:00:00 2001 From: Thanh Pham Date: Mon, 25 Dec 2023 16:12:28 +0700 Subject: [PATCH 1/4] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 39faa785..40ea809c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Fortress Web V2 -An opinionated production-ready frontend boilerplate built on top of NextJS, -shipped with TypeScript, SWR, Antd, Jest, testing-library and Storybook. +Web dashboard application for managing resources and measuring performance at https://dwarves.foundation/. ## Quick Start @@ -25,7 +24,7 @@ to see your project. | Name | Link | | ----------- | ----------------------------------------- | | Development | https://develop--fortress-v2.netlify.app/ | -| Production | https://fortress-v2.netlify.app/ | +| Production | https://fortress.d.foundation/ | ## References From 548f26d1f321d2873c0e78fb0c24ca78060de2f4 Mon Sep 17 00:00:00 2001 From: Ngo Lap Nguyen Date: Tue, 6 Aug 2024 21:41:20 +0700 Subject: [PATCH 2/4] feat: add new project detail sections --- .env.development | 3 +- .../pages/projects/add/ProjectForm.tsx | 1 - .../General/EditProjectBankAccountModal.tsx | 89 +++++++++ .../detail/General/EditProjectClientModal.tsx | 86 +++++++++ .../General/EditProjectCompanyInfoModal.tsx | 89 +++++++++ .../General/EditProjectContactInfoModal.tsx | 8 +- .../pages/projects/detail/General/index.tsx | 171 +++++++++++++++--- src/libs/apis.ts | 30 +++ src/utils/select.tsx | 30 +++ 9 files changed, 469 insertions(+), 38 deletions(-) create mode 100644 src/components/pages/projects/detail/General/EditProjectBankAccountModal.tsx create mode 100644 src/components/pages/projects/detail/General/EditProjectClientModal.tsx create mode 100644 src/components/pages/projects/detail/General/EditProjectCompanyInfoModal.tsx diff --git a/.env.development b/.env.development index b58ade92..facd4c98 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,2 @@ -# BASE_URL='https://develop-api.fortress.d.foundation/api/v1' -BASE_URL='https://api.fortress.d.foundation/api/v1' +BASE_URL='https://develop-api.fortress.d.foundation/api/v1' GOOGLE_CLIENT_ID='180094408893-s17eot91hbmqa5lp6gsgjglan9et20sp.apps.googleusercontent.com' diff --git a/src/components/pages/projects/add/ProjectForm.tsx b/src/components/pages/projects/add/ProjectForm.tsx index f97409bc..0df85f4f 100644 --- a/src/components/pages/projects/add/ProjectForm.tsx +++ b/src/components/pages/projects/add/ProjectForm.tsx @@ -116,7 +116,6 @@ export const ProjectForm = (props: Props) => { form={form} name="deliveryManagers" label="Delivery Managers" - rules={[{ required: true, message: 'Required' }]} selectProps={{ placeholder: "Select project's delivery manager" }} /> diff --git a/src/components/pages/projects/detail/General/EditProjectBankAccountModal.tsx b/src/components/pages/projects/detail/General/EditProjectBankAccountModal.tsx new file mode 100644 index 00000000..a1b69a4a --- /dev/null +++ b/src/components/pages/projects/detail/General/EditProjectBankAccountModal.tsx @@ -0,0 +1,89 @@ +import { Form, Modal, notification, Space } from 'antd' +import { useForm } from 'antd/lib/form/Form' +import { AsyncSelect } from 'components/common/Select' +import { client, GET_PATHS } from 'libs/apis' +import { useState } from 'react' +import { transformBankAccountDataToSelectOption } from 'utils/select' +import { getErrorMessage } from 'utils/string' + +// TODO: Types +type ProjectBankAccountFormValues = Partial + +interface Props { + projectID: string + isOpen: boolean + initialValues: ProjectBankAccountFormValues + onClose: () => void + onAfterSubmit: () => void +} + +export const EditProjectBankAccountModal = (props: Props) => { + const { projectID, isOpen, initialValues, onClose, onAfterSubmit } = props + + const [form] = useForm() + const [isSubmitting, setIsSubmitting] = useState(false) + + const onSubmit = async (values: ProjectBankAccountFormValues) => { + try { + setIsSubmitting(true) + + // TODO: Types + await client.updateProjectGeneralInfo(projectID, { + // NOTE: We need these values to be able to reuse the updateProjectGeneralInfo function + name: initialValues.name, + importantLevel: initialValues.importantLevel, + countryID: initialValues.country?.id, + accountRating: initialValues.accountRating, + leadRating: initialValues.leadRating, + deliveryRating: initialValues.deliveryRating, + function: initialValues.function, + ...values, + }) + + notification.success({ + message: "Project's bank account updated successfully!", + }) + + onClose() + onAfterSubmit() + } catch (error: any) { + notification.error({ + message: getErrorMessage( + error, + "Could not update project's bank account", + ), + }) + } finally { + setIsSubmitting(false) + } + } + + return ( + { + onClose() + form.resetFields() + }} + onOk={form.submit} + okButtonProps={{ loading: isSubmitting }} + destroyOnClose + title="Edit Project's Bank Account" + > +
+ + + { + const { data } = await client.getBankAccounts() + return data?.map(transformBankAccountDataToSelectOption) || [] + }} + placeholder="Select bank account" + /> + + +
+
+ ) +} diff --git a/src/components/pages/projects/detail/General/EditProjectClientModal.tsx b/src/components/pages/projects/detail/General/EditProjectClientModal.tsx new file mode 100644 index 00000000..25ae779b --- /dev/null +++ b/src/components/pages/projects/detail/General/EditProjectClientModal.tsx @@ -0,0 +1,86 @@ +import { Form, Modal, notification, Space } from 'antd' +import { useForm } from 'antd/lib/form/Form' +import { AsyncSelect } from 'components/common/Select' +import { client, GET_PATHS } from 'libs/apis' +import { useState } from 'react' +import { transformClientDataToSelectOption } from 'utils/select' +import { getErrorMessage } from 'utils/string' + +// TODO: Types +type ProjectClientFormValues = Partial + +interface Props { + projectID: string + isOpen: boolean + initialValues: ProjectClientFormValues + onClose: () => void + onAfterSubmit: () => void +} + +export const EditProjectClientModal = (props: Props) => { + const { projectID, isOpen, initialValues, onClose, onAfterSubmit } = props + + const [form] = useForm() + const [isSubmitting, setIsSubmitting] = useState(false) + + const onSubmit = async (values: ProjectClientFormValues) => { + try { + setIsSubmitting(true) + + // TODO: Types + await client.updateProjectGeneralInfo(projectID, { + // NOTE: We need these values to be able to reuse the updateProjectGeneralInfo function + name: initialValues.name, + importantLevel: initialValues.importantLevel, + countryID: initialValues.country?.id, + accountRating: initialValues.accountRating, + leadRating: initialValues.leadRating, + deliveryRating: initialValues.deliveryRating, + function: initialValues.function, + ...values, + }) + + notification.success({ + message: "Project's client updated successfully!", + }) + + onClose() + onAfterSubmit() + } catch (error: any) { + notification.error({ + message: getErrorMessage(error, "Could not update project's client"), + }) + } finally { + setIsSubmitting(false) + } + } + + return ( + { + onClose() + form.resetFields() + }} + onOk={form.submit} + okButtonProps={{ loading: isSubmitting }} + destroyOnClose + title="Edit Project's Client" + > +
+ + + { + const { data } = await client.getClients() + return data?.map(transformClientDataToSelectOption) || [] + }} + placeholder="Select client" + /> + + +
+
+ ) +} diff --git a/src/components/pages/projects/detail/General/EditProjectCompanyInfoModal.tsx b/src/components/pages/projects/detail/General/EditProjectCompanyInfoModal.tsx new file mode 100644 index 00000000..7f1e1fa4 --- /dev/null +++ b/src/components/pages/projects/detail/General/EditProjectCompanyInfoModal.tsx @@ -0,0 +1,89 @@ +import { Form, Modal, notification, Space } from 'antd' +import { useForm } from 'antd/lib/form/Form' +import { AsyncSelect } from 'components/common/Select' +import { client, GET_PATHS } from 'libs/apis' +import { useState } from 'react' +import { transformCompanyInfoDataToSelectOption } from 'utils/select' +import { getErrorMessage } from 'utils/string' + +// TODO: Types +type ProjectCompanyInfoFormValues = Partial + +interface Props { + projectID: string + isOpen: boolean + initialValues: ProjectCompanyInfoFormValues + onClose: () => void + onAfterSubmit: () => void +} + +export const EditProjectCompanyInfoModal = (props: Props) => { + const { projectID, isOpen, initialValues, onClose, onAfterSubmit } = props + + const [form] = useForm() + const [isSubmitting, setIsSubmitting] = useState(false) + + const onSubmit = async (values: ProjectCompanyInfoFormValues) => { + try { + setIsSubmitting(true) + + // TODO: Types + await client.updateProjectGeneralInfo(projectID, { + // NOTE: We need these values to be able to reuse the updateProjectGeneralInfo function + name: initialValues.name, + importantLevel: initialValues.importantLevel, + countryID: initialValues.country?.id, + accountRating: initialValues.accountRating, + leadRating: initialValues.leadRating, + deliveryRating: initialValues.deliveryRating, + function: initialValues.function, + ...values, + }) + + notification.success({ + message: "Project's company info updated successfully!", + }) + + onClose() + onAfterSubmit() + } catch (error: any) { + notification.error({ + message: getErrorMessage( + error, + "Could not update project's company info", + ), + }) + } finally { + setIsSubmitting(false) + } + } + + return ( + { + onClose() + form.resetFields() + }} + onOk={form.submit} + okButtonProps={{ loading: isSubmitting }} + destroyOnClose + title="Edit Project's Company Info" + > +
+ + + { + const { data } = await client.getCompanyInfos() + return data?.map(transformCompanyInfoDataToSelectOption) || [] + }} + placeholder="Select company info" + /> + + +
+
+ ) +} diff --git a/src/components/pages/projects/detail/General/EditProjectContactInfoModal.tsx b/src/components/pages/projects/detail/General/EditProjectContactInfoModal.tsx index e38ad9a5..64ea063d 100644 --- a/src/components/pages/projects/detail/General/EditProjectContactInfoModal.tsx +++ b/src/components/pages/projects/detail/General/EditProjectContactInfoModal.tsx @@ -1,11 +1,10 @@ import { Form, Input, Modal, notification, Space } from 'antd' import { useForm } from 'antd/lib/form/Form' +import { FormAccountWithRateList } from 'components/common/FormAccountWithRateList' import { client } from 'libs/apis' import { useState } from 'react' import { RequestUpdateContactInfoInput } from 'types/schema' import { getErrorMessage } from 'utils/string' -import { FormInputList } from 'components/common/FormInputList' -import { FormAccountWithRateList } from 'components/common/FormAccountWithRateList' type ProjectContactInfoFormValues = Partial @@ -65,7 +64,7 @@ export const EditProjectContactInfoModal = (props: Props) => { >
- { ]} addButtonProps={{ children: 'Add email' }} inputProps={{ type: 'email', placeholder: 'Enter client email' }} - /> + /> */} { form={form} name="deliveryManagers" label="Delivery Managers" - rules={[{ required: true, message: 'Required' }]} selectProps={{ placeholder: "Select project's delivery manager" }} /> { onClose: closeEditProjectContactInfoDialog, } = useDisclosure() + const { + isOpen: isEditProjectBankAccountDialogOpen, + onOpen: openEditProjectBankAccountDialog, + onClose: closeEditProjectBankAccountDialog, + } = useDisclosure() + + const { + isOpen: isEditProjectCompanyInfoDialogOpen, + onOpen: openEditProjectCompanyInfoDialog, + onClose: closeEditProjectCompanyInfoDialog, + } = useDisclosure() + + const { + isOpen: isEditProjectClientDialogOpen, + onOpen: openEditProjectClientDialog, + onClose: closeEditProjectClientDialog, + } = useDisclosure() + const mutateProject = () => { mutate([GET_PATHS.getProjects, data.code]) } @@ -183,25 +204,6 @@ export const General = (props: Props) => { > - {(data.clientEmail || []).map((mail) => ( - - {mail} - - ))} - - ) : ( - '-' - ), - permission: Permission.PROJECTS_READ_FULLACCESS, - }, { label: 'Project Email', value: data.projectEmail ? ( @@ -255,6 +257,94 @@ export const General = (props: Props) => { /> + + + + {data.bankAccount.ownerName} + + ) : ( + '-' + ), + }, + ]} + /> + + + + + + {data.companyInfo.name} + + ) : ( + '-' + ), + }, + ]} + /> + + + + + {data.client.name} + ) : ( + '-' + ), + }, + { + label: 'Address', + value: data.client ? ( + + {/* TODO: Types */} + {/* @ts-ignore */} + {data.client.address} + + ) : ( + '-' + ), + }, + { + label: 'Country', + value: data.client ? ( + + {/* TODO: Types */} + {/* @ts-ignore */} + {data.client.country} + + ) : ( + '-' + ), + }, + ]} + /> + + { onClose={closeEditProjectContactInfoDialog} onAfterSubmit={mutateProject} /> + + + ) } diff --git a/src/libs/apis.ts b/src/libs/apis.ts index 84f66572..aa6ccd01 100644 --- a/src/libs/apis.ts +++ b/src/libs/apis.ts @@ -136,6 +136,9 @@ export const GET_PATHS = { getResourceWorkUnitDistributionSummary: '/dashboards/resources/work-unit-distribution-summary', getInvoiceTemplate: '/invoices/template', + getBankAccounts: '/bank-accounts', + getCompanyInfos: '/company-infos', + getClients: '/clients', } export interface Meta { page?: number @@ -1058,6 +1061,33 @@ class Client { }, }) } + + public getBankAccounts() { + // TODO: Types + return fetcher(`${BASE_URL}/bank-accounts`, { + headers: { + ...this.privateHeaders, + }, + }) + } + + public getCompanyInfos() { + // TODO: Types + return fetcher(`${BASE_URL}/company-infos`, { + headers: { + ...this.privateHeaders, + }, + }) + } + + public getClients() { + // TODO: Types + return fetcher(`${BASE_URL}/clients`, { + headers: { + ...this.privateHeaders, + }, + }) + } } const client = new Client() diff --git a/src/utils/select.tsx b/src/utils/select.tsx index da6eb030..050167a2 100644 --- a/src/utils/select.tsx +++ b/src/utils/select.tsx @@ -63,6 +63,36 @@ export function transformOrganizationMetaDataToSelectOption( } } +// TODO: Types +export function transformBankAccountDataToSelectOption(metaItem: any) { + const { id, ownerName } = metaItem + + return { + value: id, + label: ownerName, + } +} + +// TODO: Types +export function transformCompanyInfoDataToSelectOption(metaItem: any) { + const { id, name } = metaItem + + return { + value: id, + label: name, + } +} + +// TODO: Types +export function transformClientDataToSelectOption(metaItem: any) { + const { id, name } = metaItem + + return { + value: id, + label: name, + } +} + export const searchFilterOption = ( input: string, option?: DefaultOptionType, From e0411e4a30f552a8cfed8901a0645f048aaa3b04 Mon Sep 17 00:00:00 2001 From: Ngo Lap Nguyen Date: Wed, 14 Aug 2024 10:52:42 +0700 Subject: [PATCH 3/4] feat: update --- src/components/pages/projects/detail/General/index.tsx | 4 ++-- src/pages/employees/index.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/pages/projects/detail/General/index.tsx b/src/components/pages/projects/detail/General/index.tsx index bd67762d..91acb531 100644 --- a/src/components/pages/projects/detail/General/index.tsx +++ b/src/components/pages/projects/detail/General/index.tsx @@ -266,10 +266,10 @@ export const General = (props: Props) => { - {data.bankAccount.ownerName} + {data.bankAccount.bankName} ) : ( '-' diff --git a/src/pages/employees/index.tsx b/src/pages/employees/index.tsx index 1271e620..1044081f 100644 --- a/src/pages/employees/index.tsx +++ b/src/pages/employees/index.tsx @@ -63,7 +63,7 @@ const Default = () => { ), // Only show DF employees by default // FIXME: Should not hardcode. Maybe need BE to provide this somewhere? - organizations: ['dwarves-foundation'], + organizations: ['dwarves-foundation', 'console-labs'], }), { shouldUpdateToQuery: true, From 024a2222640796c7b2fd48c414fded2276152904 Mon Sep 17 00:00:00 2001 From: Ngo Lap Nguyen Date: Tue, 20 Aug 2024 14:00:36 +0700 Subject: [PATCH 4/4] feat: make account manager optional --- src/components/pages/projects/add/ProjectForm.tsx | 1 - .../projects/detail/General/EditProjectContactInfoModal.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/components/pages/projects/add/ProjectForm.tsx b/src/components/pages/projects/add/ProjectForm.tsx index 0df85f4f..23c60a96 100644 --- a/src/components/pages/projects/add/ProjectForm.tsx +++ b/src/components/pages/projects/add/ProjectForm.tsx @@ -106,7 +106,6 @@ export const ProjectForm = (props: Props) => { form={form} name="accountManagers" label="Account Managers" - rules={[{ required: true, message: 'Required' }]} selectProps={{ placeholder: "Select project's account manager" }} /> diff --git a/src/components/pages/projects/detail/General/EditProjectContactInfoModal.tsx b/src/components/pages/projects/detail/General/EditProjectContactInfoModal.tsx index 64ea063d..cb2d6adc 100644 --- a/src/components/pages/projects/detail/General/EditProjectContactInfoModal.tsx +++ b/src/components/pages/projects/detail/General/EditProjectContactInfoModal.tsx @@ -89,7 +89,6 @@ export const EditProjectContactInfoModal = (props: Props) => { form={form} name="accountManagers" label="Account Managers" - rules={[{ required: true, message: 'Required' }]} selectProps={{ placeholder: "Select project's account manager" }} />