Skip to content

Commit 4f867e4

Browse files
authored
Merge pull request #1002 from PayButton/feat/dashboard-buttons-filter
Feat/dashboard buttons filter
2 parents 74bae36 + ffe4688 commit 4f867e4

8 files changed

Lines changed: 197 additions & 17 deletions

File tree

pages/api/dashboard/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ export default async (req: any, res: any): Promise<void> => {
1010
const userProfile = await fetchUserProfileFromId(userId)
1111
const userPreferredTimezone = userProfile?.preferredTimezone
1212
const timezone = userPreferredTimezone !== '' ? userPreferredTimezone : userReqTimezone
13-
14-
const resJSON = await CacheGet.dashboardData(userId, timezone)
13+
let buttonIds: string[] | undefined
14+
if (typeof req.query.buttonIds === 'string' && req.query.buttonIds !== '') {
15+
buttonIds = (req.query.buttonIds as string).split(',')
16+
}
17+
const resJSON = await CacheGet.dashboardData(userId, timezone, buttonIds)
1518
res.status(200).json(resJSON)
1619
}
1720
}

pages/api/payments/count/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ export default async (req: any, res: any): Promise<void> => {
2121
const totalCount = await getFilteredTransactionCount(userId, buttonIds)
2222
res.status(200).json(totalCount)
2323
} else {
24-
const resJSON = await CacheGet.paymentsCount(userId, timezone)
25-
res.status(200).json(resJSON)
24+
const totalCount = await CacheGet.paymentsCount(userId, timezone)
25+
res.status(200).json(totalCount)
2626
}
2727
} else {
2828
res.status(405).json({ error: 'Method not allowed' })

pages/dashboard/dashboard.module.css

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,93 @@
160160
font-size: 16px;
161161
}
162162
}
163+
164+
.filters_export_ctn {
165+
display: flex;
166+
align-items: center;
167+
gap: 50px;
168+
justify-content: space-between;
169+
}
170+
171+
.filters_export_ctn select {
172+
margin-bottom: 0;
173+
}
174+
175+
.show_filters_button {
176+
width: fit-content;
177+
padding: 4px 20px;
178+
border-radius: 6px;
179+
background-color: var(--secondary-bg-color);
180+
border: 1px solid var(--border-color);
181+
cursor: pointer;
182+
transition: all ease-in-out 200ms;
183+
font-weight: 500;
184+
user-select: none;
185+
display: flex;
186+
align-items: center;
187+
}
188+
189+
.show_filters_button img {
190+
margin-right: 6px;
191+
border-radius: 0;
192+
}
193+
194+
body[data-theme='dark'] .show_filters_button img {
195+
filter: brightness(0) invert(1);
196+
}
197+
198+
.show_filters_button:hover {
199+
background-color: var(--accent-color);
200+
}
201+
202+
.filters_ctn {
203+
display: flex;
204+
flex-wrap: wrap;
205+
gap: 5px;
206+
}
207+
208+
.filter_button {
209+
padding: 5px 15px;
210+
font-size: 12px;
211+
border-radius: 6px;
212+
background-color: var(--secondary-bg-color);
213+
cursor: pointer;
214+
transition: all ease-in-out 200ms;
215+
border: 1px solid var(--border-color);
216+
user-select: none;
217+
}
218+
219+
.filter_button:hover {
220+
border-color: var(--accent-color);
221+
}
222+
223+
.filter_button:active {
224+
transform: scale(0.95);
225+
}
226+
227+
.active {
228+
background-color: var(--accent-color);
229+
border-color: var(--accent-color);
230+
}
231+
232+
.showfilters_ctn {
233+
margin-top: 10px;
234+
margin-bottom: 20px;
235+
}
236+
237+
.showfilters_ctn span {
238+
font-size: 12px;
239+
margin: 10px 0 5px;
240+
display: inline-block;
241+
font-weight: 500;
242+
}
243+
244+
.wallet_label {
245+
margin-top: 20px !important;
246+
}
247+
248+
.filter_btns {
249+
display: flex;
250+
align-items: center;
251+
gap: 10px;
252+
}

pages/dashboard/index.tsx

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import { loadStateFromCookie, saveStateToCookie } from 'utils/cookies'
1313
import TopBar from 'components/TopBar'
1414
import { fetchUserWithSupertokens, UserWithSupertokens } from 'services/userService'
1515
import moment from 'moment-timezone'
16+
import SettingsIcon from '../../assets/settings-slider-icon.png'
17+
import Image from 'next/image'
18+
1619
const Chart = dynamic(async () => await import('components/Chart'), {
1720
ssr: false
1821
})
@@ -70,6 +73,9 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen
7073
const [activePeriod, setActivePeriod] = useState<PeriodData>()
7174
const [activePeriodString, setActivePeriodString] = useState<PeriodString>('1M')
7275
const [totalString, setTotalString] = useState<string>()
76+
const [selectedButtonIds, setSelectedButtonIds] = useState<any[]>([])
77+
const [showFilters, setShowFilters] = useState<boolean>(false)
78+
const [buttons, setButtons] = useState<any[]>([])
7379

7480
const setPeriodFromString = (data?: DashboardData, periodString?: PeriodString): void => {
7581
if (data === undefined) return
@@ -92,10 +98,30 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen
9298
}
9399
saveStateToCookie(COOKIE_NAMES.DASHBOARD_FILTER, periodString)
94100
}
101+
const getDataAndSetUpButtons = async (): Promise<void> => {
102+
const paybuttons = await fetchPaybuttons()
103+
setButtons(paybuttons)
104+
}
105+
const fetchPaybuttons = async (): Promise<any> => {
106+
const res = await fetch(`/api/paybuttons?userId=${user?.userProfile.id}`, {
107+
method: 'GET'
108+
})
109+
if (res.status === 200) {
110+
return await res.json()
111+
}
112+
}
113+
114+
useEffect(() => {
115+
void getDataAndSetUpButtons()
116+
}, [])
95117

96118
useEffect(() => {
97119
const fetchData = async (): Promise<void> => {
98-
const res = await fetch('api/dashboard', {
120+
let url = 'api/dashboard'
121+
if (selectedButtonIds.length > 0) {
122+
url += `?buttonIds=${selectedButtonIds.join(',')}`
123+
}
124+
const res = await fetch(url, {
99125
headers: {
100126
Timezone: moment.tz.guess()
101127
}
@@ -108,7 +134,7 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen
108134
if (savedActivePeriodString !== undefined) {
109135
setActivePeriodString(savedActivePeriodString)
110136
}
111-
}, [])
137+
}, [selectedButtonIds])
112138

113139
useEffect(() => {
114140
setPeriodFromString(dashboardData, activePeriodString)
@@ -127,6 +153,44 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen
127153
return (
128154
<>
129155
<TopBar title="Dashboard" user={user.stUser?.email} />
156+
<div className={style.filter_btns}>
157+
<div
158+
onClick={() => setShowFilters(!showFilters)}
159+
className={style.show_filters_button}
160+
>
161+
<Image src={SettingsIcon} alt="filters" width={15} />Filters
162+
</div>
163+
{selectedButtonIds.length > 0 &&
164+
<div
165+
onClick={() => setSelectedButtonIds([])}
166+
className={style.show_filters_button}
167+
>
168+
Clear
169+
</div>
170+
}
171+
</div>
172+
{showFilters && (
173+
<div className={style.showfilters_ctn}>
174+
<span>Filter by PayButton</span>
175+
<div className={style.filters_ctn}>
176+
{buttons.map((button) => (
177+
<div
178+
key={button.id}
179+
onClick={() => {
180+
setSelectedButtonIds(prev =>
181+
prev.includes(button.id)
182+
? prev.filter(id => id !== button.id)
183+
: [...prev, button.id]
184+
)
185+
}}
186+
className={`${style.filter_button} ${selectedButtonIds.includes(button.id) ? style.active : ''}`}
187+
>
188+
{button.name}
189+
</div>
190+
))}
191+
</div>
192+
</div>
193+
)}
130194
<div className={style.number_ctn}>
131195
<NumberBlock value={'$'.concat(formatQuoteValue(activePeriod.totalRevenue, user.userProfile.preferredCurrencyId)) } text='Revenue' />
132196
<NumberBlock value={activePeriod.totalPayments} text='Payments' />

redis/dashboardCache.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ const generateDashboardDataFromStream = async function (
110110
paymentStream: AsyncGenerator<Payment>,
111111
nMonthsTotal: number,
112112
borderColor: ChartColor,
113-
timezone: string
113+
timezone: string,
114+
paybuttonIds?: string[]
114115
): Promise<DashboardData> {
115116
const revenueAccumulators = createRevenueAccumulators(nMonthsTotal)
116117
const paymentCounters = createPaymentCounters(nMonthsTotal)
@@ -133,7 +134,13 @@ const generateDashboardDataFromStream = async function (
133134

134135
// Process button data and assign to relevant periods
135136
payment.buttonDisplayDataList.forEach((button) => {
136-
processButtonData(button, payment, paymentTime, buttonDataAccumulators, thresholds)
137+
if (paybuttonIds !== undefined && paybuttonIds.length > 0) {
138+
if (paybuttonIds.includes(button.id)) {
139+
processButtonData(button, payment, paymentTime, buttonDataAccumulators, thresholds)
140+
}
141+
} else {
142+
processButtonData(button, payment, paymentTime, buttonDataAccumulators, thresholds)
143+
}
137144
})
138145

139146
// Accumulate period data
@@ -151,8 +158,16 @@ const generateDashboardDataFromStream = async function (
151158
}
152159

153160
if (index >= 0 && index < revenueAccumulators[period].length) {
154-
revenueAccumulators[period][index] = sumQuoteValues(revenueAccumulators[period][index], payment.values.values)
155-
paymentCounters[period][index] += 1
161+
if (paybuttonIds !== undefined && paybuttonIds.length > 0) {
162+
const paymentButtonIds = payment.buttonDisplayDataList.map(b => b.id)
163+
if (paymentButtonIds.some(item => paybuttonIds.includes(item))) {
164+
revenueAccumulators[period][index] = sumQuoteValues(revenueAccumulators[period][index], payment.values.values)
165+
paymentCounters[period][index] += 1
166+
}
167+
} else {
168+
revenueAccumulators[period][index] = sumQuoteValues(revenueAccumulators[period][index], payment.values.values)
169+
paymentCounters[period][index] += 1
170+
}
156171
}
157172
}
158173
}
@@ -212,7 +227,8 @@ const generateDashboardDataFromStream = async function (
212227
revenue: all.totalRevenue,
213228
payments: all.totalPayments,
214229
buttons: Object.keys(buttonDataAccumulators.all).length
215-
}
230+
},
231+
filtered: paybuttonIds !== undefined && paybuttonIds.length > 0
216232
}
217233
}
218234

@@ -345,8 +361,12 @@ function createPeriodData (
345361
}
346362
}
347363

348-
export const getUserDashboardData = async function (userId: string, timezone: string): Promise<DashboardData> {
349-
const dashboardData = await getCachedDashboardData(userId)
364+
export const getUserDashboardData = async function (userId: string, timezone: string, paybuttonIds?: string[]): Promise<DashboardData> {
365+
let dashboardData = await getCachedDashboardData(userId)
366+
if ((paybuttonIds !== undefined && paybuttonIds.length > 0) ||
367+
dashboardData?.filtered === true) {
368+
dashboardData = null
369+
}
350370
if (dashboardData === null) {
351371
console.log('[CACHE]: Recreating dashboard for user', userId)
352372
const nMonthsTotal = await getNumberOfMonths(userId)
@@ -356,7 +376,8 @@ export const getUserDashboardData = async function (userId: string, timezone: st
356376
paymentStream,
357377
nMonthsTotal,
358378
{ revenue: '#66fe91', payments: '#669cfe' },
359-
timezone
379+
timezone,
380+
paybuttonIds
360381
)
361382
await cacheDashboardData(userId, dashboardData)
362383
return dashboardData

redis/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ export class CacheGet {
108108
}
109109
}
110110

111-
static async dashboardData (userId: string, timezone: string): Promise<DashboardData> {
111+
static async dashboardData (userId: string, timezone: string, buttonIds?: string[]): Promise<DashboardData> {
112112
return await this.executeCall(userId, 'dashboardData', async () => {
113-
return await getUserDashboardData(userId, timezone)
113+
return await getUserDashboardData(userId, timezone, buttonIds)
114114
})
115115
}
116116

redis/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export interface DashboardData {
3535
payments: number
3636
buttons: number
3737
}
38+
filtered: boolean
3839
}
3940

4041
export interface ButtonDisplayData {

tests/integration-tests/api.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1369,7 +1369,8 @@ describe('GET /api/dashboard', () => {
13691369
},
13701370
payments: expect.any(Number),
13711371
buttons: expect.any(Number)
1372-
}
1372+
},
1373+
filtered: expect.any(Boolean)
13731374
}
13741375
)
13751376
})

0 commit comments

Comments
 (0)