Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions pages/api/dashboard/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ export default async (req: any, res: any): Promise<void> => {
const userProfile = await fetchUserProfileFromId(userId)
const userPreferredTimezone = userProfile?.preferredTimezone
const timezone = userPreferredTimezone !== '' ? userPreferredTimezone : userReqTimezone

const resJSON = await CacheGet.dashboardData(userId, timezone)
let buttonIds: string[] | undefined
if (typeof req.query.buttonIds === 'string' && req.query.buttonIds !== '') {
buttonIds = (req.query.buttonIds as string).split(',')
}
const resJSON = await CacheGet.dashboardData(userId, timezone, buttonIds)
res.status(200).json(resJSON)
}
}
4 changes: 2 additions & 2 deletions pages/api/payments/count/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export default async (req: any, res: any): Promise<void> => {
const totalCount = await getFilteredTransactionCount(userId, buttonIds)
res.status(200).json(totalCount)
} else {
const resJSON = await CacheGet.paymentsCount(userId, timezone)
res.status(200).json(resJSON)
const totalCount = await CacheGet.paymentsCount(userId, timezone)
res.status(200).json(totalCount)
}
} else {
res.status(405).json({ error: 'Method not allowed' })
Expand Down
90 changes: 90 additions & 0 deletions pages/dashboard/dashboard.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,93 @@
font-size: 16px;
}
}

.filters_export_ctn {
display: flex;
align-items: center;
gap: 50px;
justify-content: space-between;
}

.filters_export_ctn select {
margin-bottom: 0;
}

.show_filters_button {
width: fit-content;
padding: 4px 20px;
border-radius: 6px;
background-color: var(--secondary-bg-color);
border: 1px solid var(--border-color);
cursor: pointer;
transition: all ease-in-out 200ms;
font-weight: 500;
user-select: none;
display: flex;
align-items: center;
}

.show_filters_button img {
margin-right: 6px;
border-radius: 0;
}

body[data-theme='dark'] .show_filters_button img {
filter: brightness(0) invert(1);
}

.show_filters_button:hover {
background-color: var(--accent-color);
}

.filters_ctn {
display: flex;
flex-wrap: wrap;
gap: 5px;
}

.filter_button {
padding: 5px 15px;
font-size: 12px;
border-radius: 6px;
background-color: var(--secondary-bg-color);
cursor: pointer;
transition: all ease-in-out 200ms;
border: 1px solid var(--border-color);
user-select: none;
}

.filter_button:hover {
border-color: var(--accent-color);
}

.filter_button:active {
transform: scale(0.95);
}

.active {
background-color: var(--accent-color);
border-color: var(--accent-color);
}

.showfilters_ctn {
margin-top: 10px;
margin-bottom: 20px;
}

.showfilters_ctn span {
font-size: 12px;
margin: 10px 0 5px;
display: inline-block;
font-weight: 500;
}

.wallet_label {
margin-top: 20px !important;
}

.filter_btns {
display: flex;
align-items: center;
gap: 10px;
}
68 changes: 66 additions & 2 deletions pages/dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import { loadStateFromCookie, saveStateToCookie } from 'utils/cookies'
import TopBar from 'components/TopBar'
import { fetchUserWithSupertokens, UserWithSupertokens } from 'services/userService'
import moment from 'moment-timezone'
import SettingsIcon from '../../assets/settings-slider-icon.png'
import Image from 'next/image'

const Chart = dynamic(async () => await import('components/Chart'), {
ssr: false
})
Expand Down Expand Up @@ -70,6 +73,9 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen
const [activePeriod, setActivePeriod] = useState<PeriodData>()
const [activePeriodString, setActivePeriodString] = useState<PeriodString>('1M')
const [totalString, setTotalString] = useState<string>()
const [selectedButtonIds, setSelectedButtonIds] = useState<any[]>([])
const [showFilters, setShowFilters] = useState<boolean>(false)
const [buttons, setButtons] = useState<any[]>([])

const setPeriodFromString = (data?: DashboardData, periodString?: PeriodString): void => {
if (data === undefined) return
Expand All @@ -92,10 +98,30 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen
}
saveStateToCookie(COOKIE_NAMES.DASHBOARD_FILTER, periodString)
}
const getDataAndSetUpButtons = async (): Promise<void> => {
const paybuttons = await fetchPaybuttons()
setButtons(paybuttons)
}
const fetchPaybuttons = async (): Promise<any> => {
const res = await fetch(`/api/paybuttons?userId=${user?.userProfile.id}`, {
method: 'GET'
})
if (res.status === 200) {
return await res.json()
}
}

useEffect(() => {
void getDataAndSetUpButtons()
}, [])

useEffect(() => {
const fetchData = async (): Promise<void> => {
const res = await fetch('api/dashboard', {
let url = 'api/dashboard'
if (selectedButtonIds.length > 0) {
url += `?buttonIds=${selectedButtonIds.join(',')}`
}
const res = await fetch(url, {
headers: {
Timezone: moment.tz.guess()
}
Expand All @@ -108,7 +134,7 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen
if (savedActivePeriodString !== undefined) {
setActivePeriodString(savedActivePeriodString)
}
}, [])
}, [selectedButtonIds])

useEffect(() => {
setPeriodFromString(dashboardData, activePeriodString)
Expand All @@ -127,6 +153,44 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen
return (
<>
<TopBar title="Dashboard" user={user.stUser?.email} />
<div className={style.filter_btns}>
<div
onClick={() => setShowFilters(!showFilters)}
className={style.show_filters_button}
>
<Image src={SettingsIcon} alt="filters" width={15} />Filters
</div>
{selectedButtonIds.length > 0 &&
<div
onClick={() => setSelectedButtonIds([])}
className={style.show_filters_button}
>
Clear
</div>
}
</div>
{showFilters && (
<div className={style.showfilters_ctn}>
<span>Filter by PayButton</span>
<div className={style.filters_ctn}>
{buttons.map((button) => (
<div
key={button.id}
onClick={() => {
setSelectedButtonIds(prev =>
prev.includes(button.id)
? prev.filter(id => id !== button.id)
: [...prev, button.id]
)
}}
className={`${style.filter_button} ${selectedButtonIds.includes(button.id) ? style.active : ''}`}
>
{button.name}
</div>
))}
</div>
</div>
)}
<div className={style.number_ctn}>
<NumberBlock value={'$'.concat(formatQuoteValue(activePeriod.totalRevenue, user.userProfile.preferredCurrencyId)) } text='Revenue' />
<NumberBlock value={activePeriod.totalPayments} text='Payments' />
Expand Down
37 changes: 29 additions & 8 deletions redis/dashboardCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ const generateDashboardDataFromStream = async function (
paymentStream: AsyncGenerator<Payment>,
nMonthsTotal: number,
borderColor: ChartColor,
timezone: string
timezone: string,
paybuttonIds?: string[]
): Promise<DashboardData> {
const revenueAccumulators = createRevenueAccumulators(nMonthsTotal)
const paymentCounters = createPaymentCounters(nMonthsTotal)
Expand All @@ -133,7 +134,13 @@ const generateDashboardDataFromStream = async function (

// Process button data and assign to relevant periods
payment.buttonDisplayDataList.forEach((button) => {
processButtonData(button, payment, paymentTime, buttonDataAccumulators, thresholds)
if (paybuttonIds !== undefined && paybuttonIds.length > 0) {
if (paybuttonIds.includes(button.id)) {
processButtonData(button, payment, paymentTime, buttonDataAccumulators, thresholds)
}
} else {
processButtonData(button, payment, paymentTime, buttonDataAccumulators, thresholds)
}
})

// Accumulate period data
Expand All @@ -151,8 +158,16 @@ const generateDashboardDataFromStream = async function (
}

if (index >= 0 && index < revenueAccumulators[period].length) {
revenueAccumulators[period][index] = sumQuoteValues(revenueAccumulators[period][index], payment.values.values)
paymentCounters[period][index] += 1
if (paybuttonIds !== undefined && paybuttonIds.length > 0) {
const paymentButtonIds = payment.buttonDisplayDataList.map(b => b.id)
if (paymentButtonIds.some(item => paybuttonIds.includes(item))) {
revenueAccumulators[period][index] = sumQuoteValues(revenueAccumulators[period][index], payment.values.values)
paymentCounters[period][index] += 1
}
} else {
revenueAccumulators[period][index] = sumQuoteValues(revenueAccumulators[period][index], payment.values.values)
paymentCounters[period][index] += 1
}
}
}
}
Expand Down Expand Up @@ -212,7 +227,8 @@ const generateDashboardDataFromStream = async function (
revenue: all.totalRevenue,
payments: all.totalPayments,
buttons: Object.keys(buttonDataAccumulators.all).length
}
},
filtered: paybuttonIds !== undefined && paybuttonIds.length > 0
}
}

Expand Down Expand Up @@ -345,8 +361,12 @@ function createPeriodData (
}
}

export const getUserDashboardData = async function (userId: string, timezone: string): Promise<DashboardData> {
const dashboardData = await getCachedDashboardData(userId)
export const getUserDashboardData = async function (userId: string, timezone: string, paybuttonIds?: string[]): Promise<DashboardData> {
let dashboardData = await getCachedDashboardData(userId)
if ((paybuttonIds !== undefined && paybuttonIds.length > 0) ||
dashboardData?.filtered === true) {
dashboardData = null
}
if (dashboardData === null) {
console.log('[CACHE]: Recreating dashboard for user', userId)
const nMonthsTotal = await getNumberOfMonths(userId)
Expand All @@ -356,7 +376,8 @@ export const getUserDashboardData = async function (userId: string, timezone: st
paymentStream,
nMonthsTotal,
{ revenue: '#66fe91', payments: '#669cfe' },
timezone
timezone,
paybuttonIds
)
await cacheDashboardData(userId, dashboardData)
return dashboardData
Expand Down
4 changes: 2 additions & 2 deletions redis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,9 @@ export class CacheGet {
}
}

static async dashboardData (userId: string, timezone: string): Promise<DashboardData> {
static async dashboardData (userId: string, timezone: string, buttonIds?: string[]): Promise<DashboardData> {
return await this.executeCall(userId, 'dashboardData', async () => {
return await getUserDashboardData(userId, timezone)
return await getUserDashboardData(userId, timezone, buttonIds)
})
}

Expand Down
1 change: 1 addition & 0 deletions redis/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface DashboardData {
payments: number
buttons: number
}
filtered: boolean
}

export interface ButtonDisplayData {
Expand Down
3 changes: 2 additions & 1 deletion tests/integration-tests/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1369,7 +1369,8 @@ describe('GET /api/dashboard', () => {
},
payments: expect.any(Number),
buttons: expect.any(Number)
}
},
filtered: expect.any(Boolean)
}
)
})
Expand Down