Skip to content

Commit

Permalink
Add donations filters for Admin Panel (#1268)
Browse files Browse the repository at this point in the history
* add donations filters
add useStore hook
add filter component
refactor donations grid
add the store provider
update donationList api endpoint
add donation store

* add translations

* update translations

* add types for pagination data and filterData
refactor makeObservable in donation store

* update paginationData args

* add small tweak on donationList endpoint function

Co-authored-by: Andrey <[email protected]>
  • Loading branch information
AndreyGoranov and Andrey authored Jan 6, 2023
1 parent 10db695 commit be1580e
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 22 deletions.
6 changes: 5 additions & 1 deletion public/locales/bg/donations.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
"save": "Запиши",
"open": "Отвори",
"clear": "Изчисти",
"close": "Затвори"
"close": "Затвори",
"from": "От",
"to": "До",
"status": "Статус",
"type": "Тип"
}
}
6 changes: 5 additions & 1 deletion public/locales/en/donations.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
"save": "Save",
"open": "Open",
"clear": "Clear",
"close": "Close"
"close": "Close",
"from": "From",
"to": "To",
"status": "Status",
"type": "Type"
}
}
9 changes: 7 additions & 2 deletions src/common/hooks/donation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from 'gql/donations'
import { createCheckoutSession } from 'service/donation'
import { CampaignDonationHistoryResponse } from 'gql/campaigns'
import { FilterData, PaginationData } from 'gql/types'

export function usePriceList() {
return useQuery<DonationPrice[]>([endpoints.donation.prices.url])
Expand All @@ -40,10 +41,14 @@ export function useDonationSession() {
return mutation
}

export function useDonationsList(id?: string, pageindex?: number, pagesize?: number) {
export function useDonationsList(
id?: string,
paginationData?: PaginationData,
filterData?: FilterData,
) {
const { data: session } = useSession()
return useQuery<CampaignDonationHistoryResponse>(
[endpoints.donation.donationsList(id, pageindex, pagesize).url],
[endpoints.donation.donationsList(id, paginationData, filterData).url],
{
queryFn: authQueryFnFactory(session?.accessToken),
},
Expand Down
6 changes: 6 additions & 0 deletions src/common/hooks/useStores.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { useContext } from 'react'
import { MobXProviderContext } from 'mobx-react'

export const useStores = () => {
return useContext(MobXProviderContext)
}
2 changes: 2 additions & 0 deletions src/components/donations/DonationsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import AdminContainer from 'components/admin/navigation/AdminContainer'
import AdminLayout from 'components/admin/navigation/AdminLayout'
import Grid from './grid/Grid'
import GridAppbar from './grid/GridAppbar'
import GridFilters from './grid/GridFilters'

export const ModalStore = new ModalStoreImpl()

Expand All @@ -14,6 +15,7 @@ export default function DocumentsPage() {
<AdminLayout>
<AdminContainer title={t('donations:donations')}>
<GridAppbar />
<GridFilters />
<Grid />
</AdminContainer>
</AdminLayout>
Expand Down
71 changes: 71 additions & 0 deletions src/components/donations/grid/Filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useTranslation } from 'next-i18next'
import { FormControl, InputLabel, MenuItem, Select, SelectChangeEvent } from '@mui/material'
import { Close } from '@mui/icons-material'

type Options = {
name: string
label: string
}

interface DropdownFilterProps {
options: Options
value: string
onChange: (filterName: string, filterValue: string) => void
menuItems: string[]
}

export default function DropdownFilter(props: DropdownFilterProps) {
const { t } = useTranslation()
const {
options: { name, label },
value,
onChange,
menuItems,
} = props

const handleChange = (e: SelectChangeEvent) => {
e.stopPropagation()
const filterName = e.target.name as string
const filterValue = e.target.value as string
onChange(filterName, filterValue)
}

const handleClear = (event: React.MouseEvent, filterName: string, filterValue: string) => {
event.stopPropagation()
onChange(filterName, filterValue)
}

const selectElementStyle = {
minWidth: 115,
marginRight: 1,
marginLeft: 1,
}

const closeIconStyle = {
color: 'grey',
cursor: 'pointer',
}

return (
<FormControl style={selectElementStyle}>
<InputLabel size="small">{t(label)}</InputLabel>
<Select
startAdornment={
value ? <Close style={closeIconStyle} onClick={(e) => handleClear(e, name, '')} /> : null
}
name={name}
value={value}
label={t(label)}
size="small"
onChange={(e) => handleChange(e)}>
{menuItems.map((key) => {
return (
<MenuItem key={key} value={key}>
{key}
</MenuItem>
)
})}
</Select>
</FormControl>
)
}
29 changes: 18 additions & 11 deletions src/components/donations/grid/Grid.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import { UseQueryResult } from '@tanstack/react-query'
import { useTranslation } from 'next-i18next'
import { Box, Tooltip } from '@mui/material'
Expand All @@ -25,6 +25,7 @@ import { CampaignDonationHistoryResponse } from 'gql/campaigns'
import { PersonResponse } from 'gql/person'
import { usePersonList } from 'common/hooks/person'
import RenderEditPersonCell from './RenderEditPersonCell'
import { useStores } from './../../../common/hooks/useStores'

interface RenderCellProps {
params: GridRenderCellParams
Expand All @@ -37,20 +38,27 @@ const addIconStyles = {
boxShadow: 3,
}
export default observer(function Grid() {
const [pageSize, setPageSize] = useState(5)
const [page, setPage] = useState<number>(0)
const { donationStore } = useStores()
const [paginationData, setPaginationData] = useState({
pageIndex: 0,
pageSize: 20,
})
const [focusedRowId, setFocusedRowId] = useState(null as string | null)

const { t } = useTranslation()
const router = useRouter()
const { isDetailsOpen } = ModalStore
const campaignId = router.query.campaignId as string | undefined

const {
data: { items: donations, total: all_rows } = { items: [], total: 0 },
error: donationHistoryError,
isLoading: isDonationHistoryLoading,
refetch,
}: UseQueryResult<CampaignDonationHistoryResponse> = useDonationsList(campaignId, page, pageSize)
}: UseQueryResult<CampaignDonationHistoryResponse> = useDonationsList(
campaignId,
paginationData,
donationStore.donationFilters,
)

const { data }: UseQueryResult<PersonResponse[]> = usePersonList()

Expand Down Expand Up @@ -177,7 +185,7 @@ export default observer(function Grid() {

return (
<>
<Box sx={{ marginTop: '2%', mx: 'auto', width: 700 }}>
<Box sx={{ mx: 'auto', width: 700 }}>
<DataGrid
style={{
background: 'white',
Expand All @@ -191,16 +199,15 @@ export default observer(function Grid() {
borderRadius: '0 0 13px 13px',
}}
rows={donations || []}
autoHeight
columns={columns}
rowsPerPageOptions={[5, 10, 20]}
pageSize={pageSize}
pageSize={paginationData.pageSize}
pagination
loading={isDonationHistoryLoading}
error={donationHistoryError}
page={page}
onPageChange={(params) => setPage(params)}
onPageSizeChange={(newPageSize) => setPageSize(newPageSize)}
page={paginationData.pageIndex}
onPageChange={(pageIndex) => setPaginationData({ ...paginationData, pageIndex })}
onPageSizeChange={(pageSize) => setPaginationData({ ...paginationData, pageSize })}
paginationMode="server"
rowCount={all_rows}
disableSelectionOnClick
Expand Down
81 changes: 81 additions & 0 deletions src/components/donations/grid/GridFilters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Box, TextField } from '@mui/material'
import Filter from './Filter'
import { useStores } from './../../../common/hooks/useStores'
import { observer } from 'mobx-react'
import { DonationStatus, DonationType } from 'gql/donations.enums'
import { DateTimePicker, enUS, LocalizationProvider } from '@mui/x-date-pickers'
import { useTranslation } from 'react-i18next'
import { bg } from 'date-fns/locale'
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'

export default observer(function GridFilters() {
const { donationStore } = useStores()
const { t, i18n } = useTranslation()
const donationStatusOptions = {
name: 'status',
label: 'donations:cta.status',
}

const donationStatusMenuItems = Object.values(DonationStatus)

const donationTypeOptions = {
name: 'type',
label: 'donations:cta.type',
}

const donationTypeMenuItems = Object.values(DonationType)

const handleChange = (
filterName: string,
filterValue: string | null | { from: Date; to: Date },
) => {
donationStore.setDonationFilters(filterName, filterValue)
}

const handleDatePick = (
date: Date | null | undefined | 'Invalid Date',
column: 'from' | 'to',
) => {
if ((!!date && date?.toString() !== 'Invalid Date') || date === null) {
donationStore.setDonationFilters('date', {
...donationStore.donationFilters.date,
[column]: date || '',
})
}
}

return (
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<LocalizationProvider
adapterLocale={i18n.language === 'bg' ? bg : enUS}
dateAdapter={AdapterDateFns}>
<DateTimePicker
label={t('donations:cta.from')}
value={donationStore.donationFilters.date?.from || null}
onChange={(date: Date | null | 'Invalid Date') => handleDatePick(date, 'from')}
renderInput={(params) => <TextField size="small" sx={{ marginRight: 1 }} {...params} />}
maxDate={donationStore.donationFilters.date?.to}
/>
<DateTimePicker
label={t('donations:cta.to')}
value={donationStore.donationFilters.date?.to || null}
onChange={(date: Date | null | 'Invalid Date') => handleDatePick(date, 'to')}
renderInput={(params) => <TextField size="small" sx={{ marginRight: 1 }} {...params} />}
minDate={donationStore.donationFilters.date?.from}
/>
</LocalizationProvider>
<Filter
value={donationStore.donationFilters.status}
options={donationStatusOptions}
onChange={handleChange}
menuItems={donationStatusMenuItems}
/>
<Filter
value={donationStore.donationFilters.type}
options={donationTypeOptions}
onChange={handleChange}
menuItems={donationTypeMenuItems}
/>
</Box>
)
})
11 changes: 11 additions & 0 deletions src/gql/types.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
export type UUID = string

export type PaginationData = {
pageIndex: number
pageSize: number
}

export type FilterData = {
status: DonationStatus
type: DonationType
date: { from: Date | null; to: Date | null }
}
7 changes: 6 additions & 1 deletion src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import { SessionProvider } from 'next-auth/react'
import { queryFn } from 'service/restRequests'
import 'styles/global.scss'

import { Provider } from 'mobx-react'
import { stores } from 'stores/DomainStores/stores'

// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache()

Expand Down Expand Up @@ -89,7 +92,9 @@ function CustomApp({
<SessionProvider session={session} refetchInterval={60} refetchOnWindowFocus={true}>
<QueryClientProvider client={queryClient}>
<Hydrate state={dehydratedState}>
<Component {...pageProps} />
<Provider {...stores}>
<Component {...pageProps} />
</Provider>
</Hydrate>
</QueryClientProvider>
</SessionProvider>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/admin/donations/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { endpoints } from 'service/apiEndpoints'

export const getServerSideProps = securedAdminProps(
['common', 'auth', 'admin', 'donations', 'validation'],
() => endpoints.donation.donationsList(undefined, 0, 20).url,
() => endpoints.donation.donationsList(undefined, { pageIndex: 0, pageSize: 20 }).url,
)

export default DonationsPage
21 changes: 16 additions & 5 deletions src/service/apiEndpoints.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Method } from 'axios'
import { DonationStatus } from 'gql/donations.enums'
import { FilterData, PaginationData } from 'gql/types'

type Endpoint = {
url: string
Expand Down Expand Up @@ -50,13 +51,23 @@ export const endpoints = {
createDonation: <Endpoint>{ url: '/donation/create-payment', method: 'POST' },
createBankDonation: <Endpoint>{ url: '/donation/create-bank-payment', method: 'POST' },
getDonation: (id: string) => <Endpoint>{ url: `/donation/${id}`, method: 'GET' },
donationsList: (campaignId?: string, pageindex?: number, pagesize?: number) =>
<Endpoint>{
donationsList: (
campaignId?: string,
paginationData?: PaginationData,
filterData?: FilterData,
) => {
const { pageIndex, pageSize } = (paginationData as PaginationData) || {}
const { status, type, date } = (filterData as FilterData) || {}
const { from, to } = date || {}

return <Endpoint>{
url: campaignId
? `/donation/list?campaignId=${campaignId}&pageindex=${pageindex}&pagesize=${pagesize}`
: `/donation/list?&pageindex=${pageindex}&pagesize=${pagesize}`,
? `/donation/list?campaignId=${campaignId}&pageindex=${pageIndex}&pagesize=${pageSize}&status=${status}&type=${type}&from=${from}&to=${to}`
: `/donation/list?pageindex=${pageIndex}&pagesize=${pageSize}&status=${status}&type=${type}&from=${from}&to=${to}`,
method: 'GET',
},
}
},

getDonations: (
campaignId: string,
status: DonationStatus,
Expand Down
Loading

0 comments on commit be1580e

Please sign in to comment.