Skip to content

Commit e5515fb

Browse files
committed
feat: date range filter
1 parent cc89e06 commit e5515fb

File tree

5 files changed

+177
-56
lines changed

5 files changed

+177
-56
lines changed

pages/api/payments/count/index.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,18 @@ export default async (req: any, res: any): Promise<void> => {
2020
if (typeof req.query.years === 'string' && req.query.years !== '') {
2121
years = (req.query.years as string).split(',')
2222
}
23+
let startDate: string | undefined
24+
if (typeof req.query.startDate === 'string' && req.query.startDate !== '') {
25+
startDate = req.query.startDate as string
26+
}
27+
let endDate: string | undefined
28+
if (typeof req.query.endDate === 'string' && req.query.endDate !== '') {
29+
endDate = req.query.endDate as string
30+
}
2331
if (((buttonIds !== undefined) && buttonIds.length > 0) ||
24-
((years !== undefined) && years.length > 0)) {
25-
const totalCount = await getFilteredTransactionCount(userId, buttonIds, years)
32+
((years !== undefined) && years.length > 0) ||
33+
(startDate !== undefined && endDate !== undefined && startDate !== '' && endDate !== '')) {
34+
const totalCount = await getFilteredTransactionCount(userId, buttonIds, years, timezone, startDate, endDate)
2635
res.status(200).json(totalCount)
2736
} else {
2837
const totalCount = await CacheGet.paymentsCount(userId, timezone)

pages/api/payments/download/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,16 @@ export default async (req: any, res: any): Promise<void> => {
5151
if (typeof req.query.years === 'string' && req.query.years !== '') {
5252
years = (req.query.years as string).split(',')
5353
}
54+
let startDate: string | undefined
55+
if (typeof req.query.startDate === 'string' && req.query.startDate !== '') {
56+
startDate = req.query.startDate as string
57+
}
58+
let endDate: string | undefined
59+
if (typeof req.query.endDate === 'string' && req.query.endDate !== '') {
60+
endDate = req.query.endDate as string
61+
}
5462

55-
const transactions = await fetchAllPaymentsByUserId(userId, networkIdArray, buttonIds, years)
63+
const transactions = await fetchAllPaymentsByUserId(userId, networkIdArray, buttonIds, years, startDate, endDate)
5664

5765
await downloadTxsFile(res, quoteSlug, timezone, transactions, userId)
5866
} catch (error: any) {

pages/api/payments/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ export default async (req: any, res: any): Promise<void> => {
1919
if (typeof req.query.years === 'string' && req.query.years !== '') {
2020
years = (req.query.years as string).split(',')
2121
}
22+
let startDate: string | undefined
23+
if (typeof req.query.startDate === 'string' && req.query.startDate !== '') {
24+
startDate = req.query.startDate as string
25+
}
26+
let endDate: string | undefined
27+
if (typeof req.query.endDate === 'string' && req.query.endDate !== '') {
28+
endDate = req.query.endDate as string
29+
}
2230
const userReqTimezone = req.headers.timezone as string
2331
const userProfile = await fetchUserProfileFromId(userId)
2432
const userPreferredTimezone = userProfile?.preferredTimezone
@@ -31,7 +39,9 @@ export default async (req: any, res: any): Promise<void> => {
3139
orderDesc,
3240
buttonIds,
3341
years,
34-
userPreferredTimezone ?? userReqTimezone
42+
userPreferredTimezone ?? userReqTimezone,
43+
startDate,
44+
endDate
3545
)
3646
res.status(200).json(resJSON)
3747
}

pages/payments/index.tsx

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ export default function Payments ({ user, userId, organization }: PaybuttonsProp
8686
const [invoiceMode, setInvoiceMode] = useState<'create' | 'edit' | 'view'>('create')
8787
const [isModalOpen, setIsModalOpen] = useState(false)
8888

89+
const [startDate, setStartDate] = useState<string>('')
90+
const [endDate, setEndDate] = useState<string>('')
91+
8992
const fetchNextInvoiceNumberByUserId = async (): Promise<string> => {
9093
const response = await fetch('/api/invoices/invoiceNumber/', {
9194
headers: {
@@ -136,7 +139,7 @@ export default function Payments ({ user, userId, organization }: PaybuttonsProp
136139
}
137140
useEffect(() => {
138141
setRefreshCount(prev => prev + 1)
139-
}, [selectedButtonIds, selectedTransactionYears])
142+
}, [selectedButtonIds, selectedTransactionYears, endDate])
140143

141144
const fetchPaybuttons = async (): Promise<any> => {
142145
const res = await fetch(`/api/paybuttons?userId=${user?.userProfile.id}`, {
@@ -193,19 +196,33 @@ export default function Payments ({ user, userId, organization }: PaybuttonsProp
193196
if (selectedTransactionYears.length > 0) {
194197
url += `&years=${selectedTransactionYears.join(',')}`
195198
}
199+
if (startDate !== '') {
200+
url += `&startDate=${startDate}`
201+
}
202+
if (endDate !== '') {
203+
url += `&endDate=${endDate}`
204+
}
196205

197-
const paymentsResponse = await fetch(url, {
198-
headers: {
199-
Timezone: moment.tz.guess()
200-
}
201-
})
202206
let paymentsCountUrl = '/api/payments/count'
203207
if (selectedButtonIds.length > 0) {
204208
paymentsCountUrl += `?buttonIds=${selectedButtonIds.join(',')}`
205209
}
206210
if (selectedTransactionYears.length > 0) {
207-
paymentsCountUrl += `${selectedButtonIds.length > 0 ? '&' : '?'}years=${selectedTransactionYears.join(',')}`
211+
paymentsCountUrl += `${paymentsCountUrl.includes('?') ? '&' : '?'}years=${selectedTransactionYears.join(',')}`
212+
}
213+
if (startDate !== '') {
214+
paymentsCountUrl += `${paymentsCountUrl.includes('?') ? '&' : '?'}startDate=${startDate}`
215+
}
216+
if (endDate !== '') {
217+
paymentsCountUrl += `${paymentsCountUrl.includes('?') ? '&' : '?'}endDate=${endDate}`
208218
}
219+
220+
const paymentsResponse = await fetch(url, {
221+
headers: {
222+
Timezone: moment.tz.guess()
223+
}
224+
})
225+
209226
const paymentsCountResponse = await fetch(
210227
paymentsCountUrl,
211228
{ headers: { Timezone: timezone } }
@@ -390,8 +407,14 @@ export default function Payments ({ user, userId, organization }: PaybuttonsProp
390407
if (selectedTransactionYears.length > 0) {
391408
url += `&years=${selectedTransactionYears.join(',')}`
392409
}
393-
const isCurrencyEmptyOrUndefined = (value: string): boolean => (value === '' || value === undefined)
410+
if (startDate !== '') {
411+
url += `&startDate=${startDate}`
412+
}
413+
if (endDate !== '') {
414+
url += `&endDate=${endDate}`
415+
}
394416

417+
const isCurrencyEmptyOrUndefined = (value: string): boolean => (value === '' || value === undefined)
395418
if (!isCurrencyEmptyOrUndefined(currency)) {
396419
url += `&network=${currency}`
397420
}
@@ -443,10 +466,13 @@ export default function Payments ({ user, userId, organization }: PaybuttonsProp
443466
const handleClearFilters = (): void => {
444467
setSelectedButtonIds([])
445468
setSelectedTransactionYears([])
469+
setStartDate('')
470+
setEndDate('')
446471
}
472+
447473
return (
448474
<>
449-
<TopBar title="Payments" user={user?.stUser?.email} />
475+
<TopBar title="Payments" user={user?.stUser?.email} />
450476
<div className={style.filters_export_ctn}>
451477
<div className={style.filter_btns}>
452478
<div
@@ -455,7 +481,7 @@ export default function Payments ({ user, userId, organization }: PaybuttonsProp
455481
>
456482
<Image src={SettingsIcon} alt="filters" width={15} />Filters
457483
</div>
458-
{(selectedButtonIds.length > 0 || selectedTransactionYears.length > 0) &&
484+
{(selectedButtonIds.length > 0 || selectedTransactionYears.length > 0 || startDate !== '' || endDate !== '') &&
459485
<div
460486
onClick={() => handleClearFilters()}
461487
className={style.show_filters_button}
@@ -524,11 +550,17 @@ export default function Payments ({ user, userId, organization }: PaybuttonsProp
524550
<div
525551
key={y}
526552
onClick={() => {
527-
setSelectedTransactionYears(prev =>
528-
prev.includes(y)
553+
setSelectedTransactionYears(prev => {
554+
const newYears = prev.includes(y)
529555
? prev.filter(year => year !== y)
530556
: [...prev, y]
531-
)
557+
558+
if (newYears.length > 0) {
559+
setStartDate('')
560+
setEndDate('')
561+
}
562+
return newYears
563+
})
532564
}}
533565
className={`${style.filter_button} ${selectedTransactionYears.includes(y) ? style.active : ''}`}
534566
>
@@ -537,6 +569,38 @@ export default function Payments ({ user, userId, organization }: PaybuttonsProp
537569
))}
538570
</div>
539571
</div>
572+
<div className={style.showfilters_ctn}>
573+
<span>Filter by date range</span>
574+
<div className={style.filters_ctn} style={{ alignItems: 'center' }}>
575+
<input
576+
type="date"
577+
value={startDate}
578+
onChange={(e) => {
579+
const newStartDate = e.target.value
580+
setStartDate(newStartDate)
581+
if (newStartDate !== '') {
582+
setSelectedTransactionYears([])
583+
}
584+
setEndDate('')
585+
}}
586+
className={style.filter_input}
587+
/>
588+
<span style={{ margin: '0 8px' }}>to</span>
589+
<input
590+
type="date"
591+
value={endDate}
592+
onChange={(e) => {
593+
const newEndDate = e.target.value
594+
setEndDate(newEndDate)
595+
if (newEndDate !== '') {
596+
setSelectedTransactionYears([])
597+
}
598+
}}
599+
min={startDate !== '' ? startDate : undefined}
600+
className={style.filter_input}
601+
/>
602+
</div>
603+
</div>
540604
</div>
541605
)}
542606
<TableContainerGetter

services/transactionService.ts

Lines changed: 69 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,9 @@ export async function fetchAllPaymentsByUserIdWithPagination (
757757
orderDesc = true,
758758
buttonIds?: string[],
759759
years?: string[],
760-
timezone?: string
760+
timezone?: string,
761+
startDate?: string,
762+
endDate?: string
761763
): Promise<Payment[]> {
762764
const orderDescString: Prisma.SortOrder = orderDesc ? 'desc' : 'asc'
763765

@@ -792,18 +794,15 @@ export async function fetchAllPaymentsByUserIdWithPagination (
792794

793795
const where: Prisma.TransactionWhereInput = {
794796
address: {
795-
userProfiles: {
796-
some: { userId }
797-
}
797+
userProfiles: { some: { userId } }
798798
},
799-
amount: {
800-
gt: 0
801-
}
799+
amount: { gt: 0 }
802800
}
803-
if (years !== undefined && years.length > 0) {
804-
const yearFilters = getYearFilters(years, timezone)
805801

806-
where.OR = yearFilters
802+
if (startDate !== undefined && endDate !== undefined && startDate !== '' && endDate !== '') {
803+
Object.assign(where, getDateRangeFilter(startDate, endDate, timezone))
804+
} else if (years !== undefined && years.length > 0) {
805+
where.OR = getYearFilters(years, timezone)
807806
}
808807

809808
if ((buttonIds !== undefined) && buttonIds.length > 0) {
@@ -834,29 +833,55 @@ export async function fetchAllPaymentsByUserIdWithPagination (
834833
}
835834
return transformedData
836835
}
837-
const getYearFilters = (years: string[], timezone?: string): Prisma.TransactionWhereInput[] => {
838-
return years.map((year) => {
839-
let start: number
840-
let end: number
841-
if (timezone !== undefined && timezone !== null && timezone !== '') {
842-
const startDate = new Date(`${year}-01-01T00:00:00`)
843-
const endDate = new Date(`${Number(year) + 1}-01-01T00:00:00`)
844-
const startInTimezone = new Date(startDate.toLocaleString('en-US', { timeZone: timezone }))
845-
const endInTimezone = new Date(endDate.toLocaleString('en-US', { timeZone: timezone }))
846-
const startOffset = startDate.getTime() - startInTimezone.getTime()
847-
const endOffset = endDate.getTime() - endInTimezone.getTime()
848-
start = (startDate.getTime() + startOffset) / 1000
849-
end = (endDate.getTime() + endOffset) / 1000
850-
} else {
851-
start = new Date(`${year}-01-01T00:00:00Z`).getTime() / 1000
852-
end = new Date(`${Number(year) + 1}-01-01T00:00:00Z`).getTime() / 1000
853-
}
854836

837+
const buildDateRange = (
838+
startDate: string | Date,
839+
endDate: string | Date,
840+
timezone?: string
841+
): { gte: number, lt: number } => {
842+
let start: number
843+
let end: number
844+
845+
const startObj = new Date(startDate)
846+
const endObj = new Date(endDate)
847+
848+
if (timezone !== undefined && timezone !== null && timezone !== '') {
849+
const startInTimezone = new Date(startObj.toLocaleString('en-US', { timeZone: timezone }))
850+
const endInTimezone = new Date(endObj.toLocaleString('en-US', { timeZone: timezone }))
851+
852+
const startOffset = startObj.getTime() - startInTimezone.getTime()
853+
const endOffset = endObj.getTime() - endInTimezone.getTime()
854+
855+
start = (startObj.getTime() + startOffset) / 1000
856+
end = (endObj.getTime() + endOffset) / 1000
857+
} else {
858+
start = startObj.getTime() / 1000
859+
end = endObj.getTime() / 1000
860+
}
861+
862+
return {
863+
gte: Math.floor(start),
864+
lt: Math.floor(end)
865+
}
866+
}
867+
868+
const getDateRangeFilter = (
869+
startDate: string,
870+
endDate: string,
871+
timezone?: string
872+
): Prisma.TransactionWhereInput => ({
873+
timestamp: buildDateRange(startDate, endDate, timezone)
874+
})
875+
876+
const getYearFilters = (
877+
years: string[],
878+
timezone?: string
879+
): Prisma.TransactionWhereInput[] => {
880+
return years.map((year) => {
881+
const startDate = `${year}-01-01T00:00:00`
882+
const endDate = `${Number(year) + 1}-01-01T00:00:00`
855883
return {
856-
timestamp: {
857-
gte: Math.floor(start),
858-
lt: Math.floor(end)
859-
}
884+
timestamp: buildDateRange(startDate, endDate, timezone)
860885
}
861886
})
862887
}
@@ -866,6 +891,8 @@ export async function fetchAllPaymentsByUserId (
866891
networkIds?: number[],
867892
buttonIds?: string[],
868893
years?: string[],
894+
startDate?: string,
895+
endDate?: string,
869896
timezone?: string
870897
): Promise<TransactionsWithPaybuttonsAndPrices[]> {
871898
const where: Prisma.TransactionWhereInput = {
@@ -892,10 +919,10 @@ export async function fetchAllPaymentsByUserId (
892919
}
893920
}
894921

895-
if (years !== undefined && years.length > 0) {
896-
const yearFilters = getYearFilters(years, timezone)
897-
898-
where.OR = yearFilters
922+
if (startDate !== undefined && endDate !== undefined && startDate !== '' && endDate !== '') {
923+
Object.assign(where, getDateRangeFilter(startDate, endDate, timezone))
924+
} else if (years !== undefined && years.length > 0) {
925+
where.OR = getYearFilters(years, timezone)
899926
}
900927

901928
return await prisma.transaction.findMany({
@@ -923,7 +950,9 @@ export const getFilteredTransactionCount = async (
923950
userId: string,
924951
buttonIds?: string[],
925952
years?: string[],
926-
timezone?: string
953+
timezone?: string,
954+
startDate?: string,
955+
endDate?: string
927956
): Promise<number> => {
928957
const where: Prisma.TransactionWhereInput = {
929958
address: {
@@ -942,10 +971,11 @@ export const getFilteredTransactionCount = async (
942971
}
943972
}
944973
}
945-
if (years !== undefined && years.length > 0) {
946-
const yearFilters = getYearFilters(years, timezone)
947974

948-
where.OR = yearFilters
975+
if (startDate !== undefined && endDate !== undefined && startDate !== '' && endDate !== '') {
976+
Object.assign(where, getDateRangeFilter(startDate, endDate, timezone))
977+
} else if (years !== undefined && years.length > 0) {
978+
where.OR = getYearFilters(years, timezone)
949979
}
950980

951981
return await prisma.transaction.count({ where })

0 commit comments

Comments
 (0)