From c90fa7a62050eaa95c01d1be22925047009514af Mon Sep 17 00:00:00 2001 From: quantum-grit <91589884+quantum-grit@users.noreply.github.com> Date: Tue, 20 Sep 2022 12:34:18 +0300 Subject: [PATCH] Withdrawals Create page improvements (#1026) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * renamed: тегления то преводи * replaced: статут with статус * withdrawal createForm: currency is set based on the campaign currency instead of choosing it manually * more translation renaming * fixed: campaignTitle to show in Withdrawals grid * added availableamount when selecting vault in withdrawal create form * fixed: vaultsSelect data and labels for transfers and expenses * fixed: available amount validation and moneyconversion * campaign display improvements (#1022) * updated: Stanka's photo * added: organizer mail in CampaignDetailsPage * added: email to campaign.organizer object * removed the Education/Education duplication from the campaign category/type information. Now only the type is displayed which is enough information anyway * updated: link to our team on home page * removed: jumbotron from image alt text * added: priority on campaign banner image * updated static fonts to theme based variables Co-authored-by: quantum-grit * Refactor Pricniples section, minor fixes on About us page (#1023) * Refactor Pricniples section, minor fixes on About us page * Replace sections in About us page * Add Zhana Borisova to the active members * added token expiration check in admin and profile page (#1025) Co-authored-by: quantum-grit Co-authored-by: quantum-grit Co-authored-by: Ani --- public/locales/bg/campaigns.json | 8 +- public/locales/bg/common.json | 5 +- public/locales/bg/expenses.json | 4 +- public/locales/bg/withdrawals.json | 21 ++--- public/locales/en/common.json | 5 +- public/locales/en/expenses.json | 2 +- public/locales/en/withdrawals.json | 3 +- src/components/admin/navigation/adminMenu.tsx | 4 +- src/components/campaigns/CampaignSelect.tsx | 23 ++++- src/components/common/form/FormTextField.tsx | 2 +- src/components/common/form/GenericForm.tsx | 7 +- src/components/expenses/Form.tsx | 7 +- src/components/faq/contents/campaigns.tsx | 6 +- src/components/transfers/CreateForm.tsx | 8 +- src/components/transfers/EditForm.tsx | 4 +- src/components/vaults/VaultSelect.tsx | 40 +++++++-- src/components/withdrawals/CreateForm.tsx | 88 ++++++++++++++----- src/components/withdrawals/EditForm.tsx | 7 +- src/components/withdrawals/grid/Grid.tsx | 2 +- src/gql/withdrawals.d.ts | 1 + src/service/withdrawal.ts | 6 +- 21 files changed, 171 insertions(+), 82 deletions(-) diff --git a/public/locales/bg/campaigns.json b/public/locales/bg/campaigns.json index 9b0e581ac..19938d57f 100644 --- a/public/locales/bg/campaigns.json +++ b/public/locales/bg/campaigns.json @@ -3,13 +3,13 @@ "subtitle": "Помогнете на нуждаещ се", "form-heading": "Предложете нова кампания", "edit-form-heading": "Редактирайте кампания", - "all": "Всички тегления", - "withdrawals": "Тегления", + "all": "Всички преводи", + "withdrawals": "Преводи", "name": "Име", "currency": "Валута", "amount": "Налични средства", "reason": "Причина", - "status": "Статут", + "status": "Статус", "title": "Заглавие", "essence": "Същност", "coordinator": "Кoординатор", @@ -41,7 +41,7 @@ "create": "Кампанията беше създадено успешно!", "edit": "Кампанията беше редактирано успешно!", "delete": "Кампанията беше преместено в кошчето!", - "deleteAll": "Тегленията бяха преместени в кошчето!", + "deleteAll": "Кампаниите бяха преместени в кошчето!", "error": "Възникна грешка! Моля опитайте отново по-късно.", "deletedFile": "Файлът беше изтрит успешно!" }, diff --git a/public/locales/bg/common.json b/public/locales/bg/common.json index 706293ede..b661a77ff 100644 --- a/public/locales/bg/common.json +++ b/public/locales/bg/common.json @@ -82,10 +82,7 @@ "personId": "Личност", "campaignId": "Кампания", "sourceCampaignId": "От кампания", - "targetCampaignId": "Към кампания", - "vaultId": "Трезор", - "sourceVaultId": "От трезор", - "targetVaultId": "Към трезор" + "targetCampaignId": "Към кампания" }, "cta": { "read-more": "Прочетете още", diff --git a/public/locales/bg/expenses.json b/public/locales/bg/expenses.json index e9863d384..8f145911d 100644 --- a/public/locales/bg/expenses.json +++ b/public/locales/bg/expenses.json @@ -6,7 +6,9 @@ "type": "Тип", "status": "Статус", "amount": "Стойност", - "vaultId": "Трезор Id", + "vault": "Трезор", + "sourceVault": "От трезор", + "targetVault": "Към трезор", "deleted": "Изтрит", "description": "Описание", "documentId": "Документ Id", diff --git a/public/locales/bg/withdrawals.json b/public/locales/bg/withdrawals.json index dcc17b6e2..9d73d2cc8 100644 --- a/public/locales/bg/withdrawals.json +++ b/public/locales/bg/withdrawals.json @@ -1,16 +1,17 @@ { "amount-unavailable": "Недостатъчна наличност в трезора!", "documentId": "ID на документа", + "amount-available": "Налична сума", "amount-input": "Въведете сума", - "form-heading": "Добави ново теглене", - "edit-form-heading": "Редактирай теглене", - "all": "Всички тегления", - "withdrawals": "Тегления", + "form-heading": "Създай нов превод", + "edit-form-heading": "Редактирай превод", + "all": "Тук отчитаме преводите, които се правят, когато събраните пари по дадена кампания се превеждат към бенефициента.", + "withdrawals": "Преводи", "name": "Име", "currency": "Валута", "amount": "Сума", "reason": "Причина", - "status": "Статут", + "status": "Статус", "targetDate": "Целева дата", "createdAt": "Създаден на", "updatedAt": "Обновен на", @@ -25,10 +26,10 @@ "actions": "Действия", "alerts": { "selectRow": "Моля изберете ред", - "create": "Тегленето беше създадено успешно!", - "edit": "Тегленето беше редактирано успешно!", - "delete": "Тегленето беше преместено в кошчето!", - "deleteAll": "Тегленията бяха преместени в кошчето!", + "create": "Преводът беше създаден успешно!", + "edit": "Преводът беше редактиран успешно!", + "delete": "Преводът беше преместен в кошчето!", + "deleteAll": "Преводите бяха преместени в кошчето!", "error": "Възникна грешка! Моля опитайте отново по-късно." }, "cta": { @@ -38,7 +39,7 @@ "delete": "Изтрий", "deleteSelected": "Изтрий избраните редове", "edit": "Редактирай", - "details": "Детайли за тегленето", + "details": "Детайли за превода", "submit": "Изпрати" }, "fields": { diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 8e67d2640..2d518bfa5 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -80,11 +80,8 @@ "fields": { "currency": "Currency", "personId": "Person", - "vaultId": "Vault", "sourceCampaignId": "Source campaign", - "targetCampaignId": "Target campaign", - "sourceVaultId": "Source vault", - "targetVaultId": "Target vault" + "targetCampaignId": "Target campaign" }, "cta": { "read-more": "Read more", diff --git a/public/locales/en/expenses.json b/public/locales/en/expenses.json index 1e9543dcf..7451c6501 100644 --- a/public/locales/en/expenses.json +++ b/public/locales/en/expenses.json @@ -6,7 +6,7 @@ "type": "Type", "status": "Status", "amount": "Amount", - "vaultId": "Vault Id", + "vault": "Vault", "deleted": "Deleted", "description": "Description", "documentId": "Document Id", diff --git a/public/locales/en/withdrawals.json b/public/locales/en/withdrawals.json index 07299ff0c..2a59d288f 100644 --- a/public/locales/en/withdrawals.json +++ b/public/locales/en/withdrawals.json @@ -1,7 +1,8 @@ { "amount-unavailable": "Insufficient stock in the vault!", "documentId": "Document ID", - "amount-input": "Amount", + "amount-available": "Available amount", + "amount-input": "Withdrawal amount", "form-heading": "Add new withdrawal", "edit-form-heading": "Edit withdrawal", "all": "All withdrawals", diff --git a/src/components/admin/navigation/adminMenu.tsx b/src/components/admin/navigation/adminMenu.tsx index 5ab837c3a..5dd8ac1a1 100644 --- a/src/components/admin/navigation/adminMenu.tsx +++ b/src/components/admin/navigation/adminMenu.tsx @@ -41,7 +41,7 @@ export const menuPayments = [ { label: 'Дарения', icon: VolunteerActivismOutlinedIcon, href: routes.admin.donations.index }, { label: 'Трезори', icon: Shield, href: routes.admin.vaults.index }, { label: 'Банкови сметки', icon: Payment, href: routes.admin.bankaccounts.index }, - { label: 'Тегления', icon: LocalAtmIcon, href: routes.admin.withdrawals.index }, + { label: 'Преводи', icon: LocalAtmIcon, href: routes.admin.withdrawals.index }, { label: 'Прехвърляния', icon: MoveUp, href: routes.admin.transfer.index }, { label: 'Разходи', icon: Paid, href: routes.admin.expenses.index }, { @@ -72,7 +72,7 @@ export const adminCards = [ { label: 'Организатори', icon: Group, href: routes.admin.organizers.index }, { label: 'ЮЛНЦ', icon: Group, href: routes.admin.company.create }, { label: 'Банкови сметки', icon: Payment, href: routes.admin.bankaccounts.index }, - { label: 'Тегления', icon: LocalAtmIcon, href: routes.admin.withdrawals.index }, + { label: 'Преводи', icon: LocalAtmIcon, href: routes.admin.withdrawals.index }, { label: 'Разходи', icon: Paid, href: routes.admin.expenses.index }, { label: 'Трезори', icon: Shield, href: routes.admin.vaults.index }, { label: 'Прехвърляния', icon: MoveUp, href: routes.admin.transfer.index }, diff --git a/src/components/campaigns/CampaignSelect.tsx b/src/components/campaigns/CampaignSelect.tsx index 19f4e8148..1533710e9 100644 --- a/src/components/campaigns/CampaignSelect.tsx +++ b/src/components/campaigns/CampaignSelect.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { useField } from 'formik' +import { useField, useFormikContext } from 'formik' import { useTranslation } from 'next-i18next' import { FormControl, MenuItem, TextFieldProps } from '@mui/material' @@ -8,16 +8,32 @@ import { CampaignResponse } from 'gql/campaigns' import FormTextField from 'components/common/form/FormTextField' +export type SetFieldValueType = (field: string, value: unknown, shouldValidate?: boolean) => void + type Props = { label: string name: string campaigns?: CampaignResponse[] + handleCampaignSelected?: (campaignId: string, setFieldValue: SetFieldValueType) => void } & TextFieldProps -export default function CampaignSelect({ label, name, campaigns, ...textFieldProps }: Props) { +export default function CampaignSelect({ + label, + name, + campaigns, + handleCampaignSelected, + ...textFieldProps +}: Props) { const { t } = useTranslation() const [field, meta] = useField(name) + const { setFieldValue } = useFormikContext() + + const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => { + setFieldValue(name, event.target.value) + + if (handleCampaignSelected) handleCampaignSelected(event.target.value as string, setFieldValue) + } return ( + onChange={handleChange}> {t(label)} diff --git a/src/components/common/form/FormTextField.tsx b/src/components/common/form/FormTextField.tsx index c0ae36f9d..7b426a48f 100644 --- a/src/components/common/form/FormTextField.tsx +++ b/src/components/common/form/FormTextField.tsx @@ -24,8 +24,8 @@ export default function FormTextField({ label, name, ...textFieldProps }: Regist variant="outlined" error={Boolean(meta.error) && Boolean(meta.touched)} helperText={helperText} + {...field} //this and the following needs to be in exact this order for the onChange event to not be overriden and silenced {...textFieldProps} - {...field} /> ) } diff --git a/src/components/common/form/GenericForm.tsx b/src/components/common/form/GenericForm.tsx index 079806ca8..ae21fb7cc 100644 --- a/src/components/common/form/GenericForm.tsx +++ b/src/components/common/form/GenericForm.tsx @@ -1,9 +1,12 @@ -import { Form, Formik, FormikConfig } from 'formik' +import { Form, Formik, FormikConfig, FormikValues } from 'formik' import React, { PropsWithChildren } from 'react' export type GenericFormProps = PropsWithChildren> -export default function GenericForm({ children, ...formProps }: GenericFormProps) { +export default function GenericForm({ + children, + ...formProps +}: GenericFormProps) { return ( {...formProps}>
{children}
diff --git a/src/components/expenses/Form.tsx b/src/components/expenses/Form.tsx index a27580b9c..f02672f6f 100644 --- a/src/components/expenses/Form.tsx +++ b/src/components/expenses/Form.tsx @@ -151,7 +151,12 @@ export default function Form() { - + diff --git a/src/components/faq/contents/campaigns.tsx b/src/components/faq/contents/campaigns.tsx index 1902bdf6c..05511e0f1 100644 --- a/src/components/faq/contents/campaigns.tsx +++ b/src/components/faq/contents/campaigns.tsx @@ -229,7 +229,7 @@ export const CAMPAIGN_QUESTIONS: ContentType[] = [ ), }, { - header: 'В какъв етап на одобрение се намира кампанията ми? С какъв статут е към момента?', + header: 'В какъв етап на одобрение се намира кампанията ми? С какъв статус е към момента?', content: ( Пишете ни на: info@podkrepi.bg и ще Ви информираме при първа възможност. @@ -284,7 +284,7 @@ export const CAMPAIGN_QUESTIONS: ContentType[] = [ ), }, { - header: 'Защо кампанията ми е със статут “Успешно завършена”?', + header: 'Защо кампанията ми е със статус “Успешно завършена”?', content: ( Това означава, че финансовата цел на кампанията е постигната и средствата са събрани. @@ -295,7 +295,7 @@ export const CAMPAIGN_QUESTIONS: ContentType[] = [ ), }, { - header: 'Защо кампанията ми е със статут “Прекратена”?', + header: 'Защо кампанията ми е със статус “Прекратена”?', content: ( Причините може да са 2: diff --git a/src/components/transfers/CreateForm.tsx b/src/components/transfers/CreateForm.tsx index 791b36b35..106800269 100644 --- a/src/components/transfers/CreateForm.tsx +++ b/src/components/transfers/CreateForm.tsx @@ -25,7 +25,7 @@ import { TransferStatus } from './TransferTypes' import CampaignSelect from '../campaigns/CampaignSelect' import { useVaultsList } from 'common/hooks/vaults' import PersonSelect from 'components/person/PersonSelect' -import { toMoney } from 'common/util/money' +import { fromMoney, toMoney } from 'common/util/money' type Props = { campaigns: CampaignResponse[] @@ -67,7 +67,7 @@ export default function CreateForm({ campaigns }: Props) { test: function (value) { const currentValt = vaults?.find((curr) => curr.id == this.parent.sourceVaultId) const currentAmount = Number(currentValt?.amount) - Number(currentValt?.blockedAmount) - return value! < Number(currentAmount) + return value ? value < Number(fromMoney(currentAmount)) : false }, }), otherwise: yup.number().positive().integer().required(), @@ -149,7 +149,7 @@ export default function CreateForm({ campaigns }: Props) { /> - + - + diff --git a/src/components/transfers/EditForm.tsx b/src/components/transfers/EditForm.tsx index c92b257d7..7231de183 100644 --- a/src/components/transfers/EditForm.tsx +++ b/src/components/transfers/EditForm.tsx @@ -160,7 +160,7 @@ export default function EditForm({ transfer, campaigns, id }: Props) { /> - + - + diff --git a/src/components/vaults/VaultSelect.tsx b/src/components/vaults/VaultSelect.tsx index e77473794..372693569 100644 --- a/src/components/vaults/VaultSelect.tsx +++ b/src/components/vaults/VaultSelect.tsx @@ -1,15 +1,36 @@ import { useTranslation } from 'react-i18next' -import { FormControl, MenuItem } from '@mui/material' -import { useField } from 'formik' +import { FormControl, MenuItem, TextFieldProps } from '@mui/material' +import { useField, useFormikContext } from 'formik' -import { useVaultsList } from 'common/hooks/vaults' import FormTextField from 'components/common/form/FormTextField' +import { VaultResponse } from 'gql/vault' -export default function VaultSelect({ name = 'vaultId', ...textFieldProps }) { +export type SetFieldValueType = (field: string, value: unknown, shouldValidate?: boolean) => void + +type Props = { + label: string + name: string + vaults?: VaultResponse[] + handleVaultSelected?: (vaultId: string, setFieldValue: SetFieldValueType) => void +} & TextFieldProps + +export default function VaultSelect({ + label, + name, + vaults, + handleVaultSelected, + ...textFieldProps +}: Props) { const { t } = useTranslation() - const { data: values } = useVaultsList() const [field, meta] = useField(name) + const { setFieldValue } = useFormikContext() + + const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => { + setFieldValue(name, event.target.value) + + if (handleVaultSelected) handleVaultSelected(event.target.value as string, setFieldValue) + } return ( + {...textFieldProps} + onChange={handleChange}> {t('fields.' + name)} - {values?.map((value, index) => ( + {vaults?.map((value, index) => ( - {value.id} + {value.name} ))} diff --git a/src/components/withdrawals/CreateForm.tsx b/src/components/withdrawals/CreateForm.tsx index 207d17679..ff2330255 100644 --- a/src/components/withdrawals/CreateForm.tsx +++ b/src/components/withdrawals/CreateForm.tsx @@ -18,13 +18,12 @@ import { useCampaignList } from 'common/hooks/campaigns' import { useVaultsList } from 'common/hooks/vaults' import GenericForm from 'components/common/form/GenericForm' import FormTextField from 'components/common/form/FormTextField' -import CurrencySelect from 'components/currency/CurrencySelect' import BankAccountSelect from 'components/bankaccounts/BankAccountSelect' -import CampaignSelect from 'components/campaigns/CampaignSelect' +import CampaignSelect, { SetFieldValueType } from 'components/campaigns/CampaignSelect' import VaultSelect from 'components/vaults/VaultSelect' import PersonSelect from 'components/person/PersonSelect' import { Currency } from 'gql/currency' -import { toMoney } from 'common/util/money' +import { fromMoney, toMoney } from 'common/util/money' export default function CreateForm() { const router = useRouter() @@ -48,9 +47,10 @@ export default function CreateForm() { params: {}, message: t('amount-unavailable'), test: function (value) { - const currentValt = vaults?.find((curr) => curr.id == this.parent.sourceVaultId) - const currentAmount = Number(currentValt?.amount) - Number(currentValt?.blockedAmount) - return value! < Number(currentAmount) + const currentVault = vaults?.find((curr) => curr.id === this.parent.sourceVaultId) + const currentAmount = + Number(currentVault?.amount) - Number(currentVault?.blockedAmount) + return Number(value) < Number(fromMoney(currentAmount)) }, }), otherwise: yup.number().positive().integer().required(), @@ -67,6 +67,7 @@ export default function CreateForm() { status: WithdrawalStatus.initial, currency: '', amount: 0, + amountAvailable: 0, reason: '', sourceVaultId: '', sourceCampaignId: '', @@ -80,7 +81,7 @@ export default function CreateForm() { const mutation = useMutation< AxiosResponse, AxiosError, - WithdrawalInput + WithdrawalData >({ mutationFn, onError: () => AlertStore.show(t('withdrawals:alerts:error'), 'error'), @@ -91,7 +92,7 @@ export default function CreateForm() { }) function handleSubmit(values: WithdrawalInput) { - const data: WithdrawalInput = { + const data: WithdrawalData = { status: WithdrawalStatus.initial, currency: values.currency, amount: toMoney(values.amount), @@ -105,8 +106,25 @@ export default function CreateForm() { mutation.mutate(data) } + function handleCampaignSelected(campaignId: string, setFieldValue: SetFieldValueType) { + const selectedCampaign = campaigns?.find((campaign) => campaign.id === campaignId) + if (selectedCampaign) { + setFieldValue('currency', selectedCampaign?.currency) + } + } + + function handleVaultSelected(vaultId: string, setFieldValue: SetFieldValueType) { + const selectedVault = vaults?.find((vault) => vault.id === vaultId) + if (selectedVault) { + setFieldValue( + 'amountAvailable', + fromMoney(selectedVault.amount - selectedVault.blockedAmount), + ) + } + } + return ( - onSubmit={handleSubmit} initialValues={initialValues} validationSchema={validationSchema}> @@ -115,6 +133,42 @@ export default function CreateForm() { {t('form-heading')} + + + + + + + + + + + + - - - - + + @@ -140,16 +192,6 @@ export default function CreateForm() { autoComplete="documentId" /> - - - - - - diff --git a/src/components/withdrawals/EditForm.tsx b/src/components/withdrawals/EditForm.tsx index 0cb46414c..7a2be21ac 100644 --- a/src/components/withdrawals/EditForm.tsx +++ b/src/components/withdrawals/EditForm.tsx @@ -61,6 +61,7 @@ export default function EditForm() { status: WithdrawalStatus.initial, currency: data?.currency, amount: fromMoney(data?.amount ?? 0), + amountAvailable: 0, reason: data?.reason, sourceVaultId: data?.sourceVaultId, sourceCampaignId: data?.sourceCampaignId, @@ -72,7 +73,7 @@ export default function EditForm() { const mutation = useMutation< AxiosResponse, AxiosError, - WithdrawalInput + WithdrawalData >({ mutationFn, onError: () => AlertStore.show(t('withdrawals:alerts:error'), 'error'), @@ -84,7 +85,7 @@ export default function EditForm() { }) function handleSubmit(values: WithdrawalInput) { - const data: WithdrawalInput = { + const data: WithdrawalData = { status: WithdrawalStatus.initial, currency: values.currency, amount: toMoney(values.amount), @@ -143,7 +144,7 @@ export default function EditForm() { /> - + diff --git a/src/components/withdrawals/grid/Grid.tsx b/src/components/withdrawals/grid/Grid.tsx index bfb6500f3..25344784e 100644 --- a/src/components/withdrawals/grid/Grid.tsx +++ b/src/components/withdrawals/grid/Grid.tsx @@ -90,7 +90,7 @@ export default observer(function Grid() { headerName: t('withdrawals:sourceCampaign'), ...commonProps, valueGetter: (c) => { - return c.row.sourceCampaign.state + return c.row.sourceCampaign.title }, }, { diff --git a/src/gql/withdrawals.d.ts b/src/gql/withdrawals.d.ts index d8e2881fa..0feb6258f 100644 --- a/src/gql/withdrawals.d.ts +++ b/src/gql/withdrawals.d.ts @@ -30,6 +30,7 @@ export type WithdrawalInput = { status: WithdrawalStatus | undefined currency: Currency | undefined amount: number + amountAvailable: number reason: string | undefined documentId?: UUID | undefined approvedById?: UUID | undefined diff --git a/src/service/withdrawal.ts b/src/service/withdrawal.ts index 85c09c8e1..90b3d7f0b 100644 --- a/src/service/withdrawal.ts +++ b/src/service/withdrawal.ts @@ -2,14 +2,14 @@ import { AxiosResponse } from 'axios' import { useSession } from 'next-auth/react' import { apiClient } from 'service/apiClient' -import { WithdrawalResponse, WithdrawalInput } from 'gql/withdrawals' +import { WithdrawalResponse, WithdrawalData } from 'gql/withdrawals' import { endpoints } from './apiEndpoints' import { authConfig } from './restRequests' export function useCreateWithdrawal() { const { data: session } = useSession() - return async (data: WithdrawalInput) => { + return async (data: WithdrawalData) => { return await apiClient.post>( endpoints.withdrawals.createWithdrawal.url, data, @@ -20,7 +20,7 @@ export function useCreateWithdrawal() { export function useEditWithdrawal(slug: string) { const { data: session } = useSession() - return async (data: WithdrawalInput) => { + return async (data: WithdrawalData) => { return await apiClient.patch>( endpoints.withdrawals.editWithdrawal(slug).url, data,