diff --git a/public/locales/bg/donations.json b/public/locales/bg/donations.json index a1ca47542..9b57eae78 100644 --- a/public/locales/bg/donations.json +++ b/public/locales/bg/donations.json @@ -24,13 +24,16 @@ "createdAt": "Направено на", "bankTransactionsFileId": "Номер на файла с банкови транзакции", "addFiles": "Добави файлове", + "noOptions": "Няма резултати", "alerts": { "selectRow": "Моля изберете ред", "create": "Дарението беше създадено успешно!", "edit": "Дарението беше редактирано успешно!", "delete": "Дарението беше изтрито успешно!", "deleteAll": "Даренията бяха изтрити успешно!", - "error": "Възникна грешка! Моля опитайте отново по-късно." + "editDonor": "Дарителят беше редактиран успешно!", + "error": "Възникна грешка! Моля опитайте отново по-късно.", + "requiredError": "Полето е задължително." }, "cta": { "add": "Добави", @@ -40,6 +43,10 @@ "deleteSelected": "Изтрий избраните редове", "edit": "Редактирай", "details": "Детайли за дарение", - "submit": "Изпрати" + "submit": "Изпрати", + "save": "Запиши", + "open": "Отвори", + "clear": "Изчисти", + "close": "Затвори" } } diff --git a/public/locales/en/donations.json b/public/locales/en/donations.json index 7ceb2a18d..19c071502 100644 --- a/public/locales/en/donations.json +++ b/public/locales/en/donations.json @@ -24,13 +24,16 @@ "addFiles": "Add files", "actions": "Actions", "createdAt": "Created at", + "noOptions": "No results", "alerts": { "selectRow": "Please choose a row", "create": "Document has been created successfully!", "edit": "Document has been edited successfully!", "delete": "Document has been deleted successfully!", "deleteAll": "Documents have been deleted successfully!", - "error": "An error has occured! Please try again later." + "editDonor": "Donation donor has been edited successfully!", + "error": "An error has occured! Please try again later.", + "requiredError": "Fields is required!" }, "cta": { "add": "Add", @@ -40,6 +43,10 @@ "deleteSelected": "Delete selected rows", "edit": "Edit", "details": "Donation Details", - "submit": "Submit" + "submit": "Submit", + "save": "Save", + "open": "Open", + "clear": "Clear", + "close": "Close" } } diff --git a/src/components/donations/grid/Grid.tsx b/src/components/donations/grid/Grid.tsx index 226361180..3faa2fb72 100644 --- a/src/components/donations/grid/Grid.tsx +++ b/src/components/donations/grid/Grid.tsx @@ -1,13 +1,19 @@ import React, { useState } from 'react' import { UseQueryResult } from 'react-query' import { useTranslation } from 'next-i18next' -import { Box } from '@mui/material' -import { DataGrid, GridColDef, GridColumns, GridRenderCellParams } from '@mui/x-data-grid' +import { Box, Tooltip } from '@mui/material' +import { Edit } from '@mui/icons-material' +import { + DataGrid, + GridCellModes, + GridColDef, + GridColumns, + GridRenderCellParams, + GridRenderEditCellParams, +} from '@mui/x-data-grid' import { observer } from 'mobx-react' -import { routes } from 'common/routes' import { useDonationsList } from 'common/hooks/donation' -import GridActions from 'components/admin/GridActions' import DetailsModal from '../modals/DetailsModal' import DeleteModal from '../modals/DeleteModal' @@ -16,14 +22,25 @@ import { getExactDateTime } from 'common/util/date' import { useRouter } from 'next/router' import { money } from 'common/util/money' import { CampaignDonationHistoryResponse } from 'gql/campaigns' +import { PersonResponse } from 'gql/person' +import { usePersonList } from 'common/hooks/person' +import RenderEditPersonCell from './RenderEditPersonCell' interface RenderCellProps { params: GridRenderCellParams } - +const addIconStyles = { + background: '#4ac3ff', + borderRadius: '50%', + cursor: 'pointer', + padding: 0.7, + boxShadow: 3, +} export default observer(function Grid() { const [pageSize, setPageSize] = useState(5) const [page, setPage] = useState(0) + const [focusedRowId, setFocusedRowId] = useState(null as string | null) + const { t } = useTranslation() const router = useRouter() const { isDetailsOpen } = ModalStore @@ -32,8 +49,10 @@ export default observer(function Grid() { data: { items: donations, total: all_rows } = { items: [], total: 0 }, error: donationHistoryError, isLoading: isDonationHistoryLoading, + refetch, }: UseQueryResult = useDonationsList(campaignId, page, pageSize) + const { data }: UseQueryResult = usePersonList() const RenderVaultCell = ({ params }: RenderCellProps) => { return <>{params.row.targetVault.name} } @@ -41,7 +60,31 @@ export default observer(function Grid() { const { firstName, lastName } = params.row.person ? params.row.person : { firstName: 'Anonymous', lastName: 'Donor' } - return <>{firstName + ' ' + lastName} + return ( + <> + + {firstName + ' ' + lastName} + {params.isEditable ? ( + + { + if (focusedRowId) { + params.api.setCellMode(focusedRowId, params.field, GridCellModes.View) + } + params.api.setCellMode(params.row.id, params.field, GridCellModes.Edit) + setFocusedRowId(params.row.id) + }} + /> + + ) : ( + <> + )} + + + ) } const RenderMoneyCell = ({ params }: RenderCellProps) => { @@ -85,10 +128,14 @@ export default observer(function Grid() { field: 'person', headerName: t('donations:person'), ...commonProps, - width: 250, + editable: true, + width: 280, renderCell: (params: GridRenderCellParams) => { return }, + renderEditCell: (params: GridRenderEditCellParams) => { + return + }, }, { field: 'billingName', @@ -155,6 +202,7 @@ export default observer(function Grid() { paginationMode="server" rowCount={all_rows} disableSelectionOnClick + isCellEditable={(params) => params.row.provider.includes('bank')} /> diff --git a/src/components/donations/grid/RenderEditPersonCell.tsx b/src/components/donations/grid/RenderEditPersonCell.tsx new file mode 100644 index 000000000..615697536 --- /dev/null +++ b/src/components/donations/grid/RenderEditPersonCell.tsx @@ -0,0 +1,105 @@ +import React from 'react' +import { AxiosError, AxiosResponse } from 'axios' +import { useMutation } from 'react-query' +import { useTranslation } from 'next-i18next' +import { GridRenderEditCellParams, GridCellModes } from '@mui/x-data-grid' +import { Autocomplete, createFilterOptions, TextField, Tooltip, Box } from '@mui/material' +import { Save } from '@mui/icons-material' + +import { PersonResponse } from 'gql/person' +import { DonationResponse, UserDonationInput } from 'gql/donations' +import { useEditDonation } from 'service/donation' +import { ApiErrors } from 'service/apiErrors' +import { AlertStore } from 'stores/AlertStore' + +interface RenderEditCellProps { + params: GridRenderEditCellParams + personList?: PersonResponse[] + onUpdate(): void +} +const addIconStyles = { + background: '#4ac3ff', + borderRadius: '50%', + cursor: 'pointer', + padding: 0.7, + boxShadow: 3, +} + +export default function RenderEditPersonCell({ + params, + personList, + onUpdate, +}: RenderEditCellProps) { + const { t } = useTranslation() + + const initialPerson = { + firstName: + params.row.person && params.row.person.firstName ? params.row.person.firstName : 'Anonymous', + lastName: + params.row.person && params.row.person.lastName ? params.row.person.lastName : 'Donor', + email: params.row.email || params.row.billingEmail || null, + } + const [person, setPerson] = React.useState({ + ...initialPerson, + } as PersonResponse) + const mutationFn = useEditDonation(params.row.id) + + const mutation = useMutation< + AxiosResponse, + AxiosError, + UserDonationInput + >({ + mutationFn, + onError: () => AlertStore.show(t('donations:alerts.error'), 'error'), + onSuccess: () => { + AlertStore.show(t('donations:alerts.editDonor'), 'success') + onUpdate() + params.api.setCellMode(params.row.id, params.field, GridCellModes.View) + }, + }) + + const onClick = () => { + if (person) { + const donationData: UserDonationInput = params.row + donationData.targetPersonId = person.id + mutation.mutate(donationData) + } else { + AlertStore.show(t('donations:alerts.requiredError'), 'error') + } + } + + return ( + + { + setPerson(newValue) + }} + options={personList || []} + getOptionLabel={(option: PersonResponse) => `${option.firstName} ${option.lastName}`} + renderInput={(params) => } + renderOption={(params, option: PersonResponse) => ( + + {`${option.firstName} ${option.lastName} (${option.email ? option.email : ''})`} + + )} + isOptionEqualToValue={(option, value) => option.firstName === value.firstName} + filterOptions={createFilterOptions({ + matchFrom: 'any', + limit: 5, + ignoreCase: true, + trim: true, + })} + clearText={t('donations:cta.clear')} + noOptionsText={t('donations:noOptions')} + openText={t('donations:cta.open')} + closeText={t('donations:cta.close')} + /> + + + + + ) +} diff --git a/src/gql/donations.d.ts b/src/gql/donations.d.ts index 7818a0baa..6b5ce1b94 100644 --- a/src/gql/donations.d.ts +++ b/src/gql/donations.d.ts @@ -60,6 +60,10 @@ export type DonationInput = { extPaymentMethodId: string // personId: UUID } +export type UserDonationInput = DonationInput & { + targetPersonId?: UUID +} + export type DonationBankInput = { currency: string amount: number diff --git a/src/service/donation.ts b/src/service/donation.ts index 579c21d58..dcaea1e78 100644 --- a/src/service/donation.ts +++ b/src/service/donation.ts @@ -8,6 +8,7 @@ import { DonationBankInput, DonationInput, DonationResponse, + UserDonationInput, } from 'gql/donations' import { apiClient } from 'service/apiClient' import { endpoints } from 'service/apiEndpoints' @@ -44,7 +45,7 @@ export function useCreateBankDonation() { export function useEditDonation(id: string) { const { data: session } = useSession() - return async (data: DonationInput) => { + return async (data: UserDonationInput) => { return await apiClient.patch>( endpoints.donation.editDonation(id).url, data, diff --git a/yarn.lock b/yarn.lock index 2dacbb380..d57dc5531 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6266,11 +6266,6 @@ pegjs@^0.10.0: resolved "https://registry.yarnpkg.com/pegjs/-/pegjs-0.10.0.tgz#cf8bafae6eddff4b5a7efb185269eaaf4610ddbd" integrity sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0= -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - picocolors@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" @@ -6595,13 +6590,6 @@ quill@^1.3.7, quill@^1.x: parchment "^1.1.4" quill-delta "^3.6.2" -raf@^3.4.0: - version "3.4.1" - resolved "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz" - integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== - dependencies: - performance-now "^2.1.0" - ramda@^0.26.1: version "0.26.1" resolved "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz"