Skip to content

Commit

Permalink
Use tRPC in group activity
Browse files Browse the repository at this point in the history
  • Loading branch information
scastiel committed Oct 20, 2024
1 parent 74c7b76 commit b1b2918
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 73 deletions.
11 changes: 6 additions & 5 deletions src/app/groups/[groupId]/activity/activity-item.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
'use client'
import { Button } from '@/components/ui/button'
import { getGroupExpenses } from '@/lib/api'
import { DateTimeStyle, cn, formatDate } from '@/lib/utils'
import { Activity, ActivityType, Participant } from '@prisma/client'
import { AppRouterOutput } from '@/trpc/routers/_app'
import { ActivityType, Participant } from '@prisma/client'
import { ChevronRight } from 'lucide-react'
import { useLocale, useTranslations } from 'next-intl'
import Link from 'next/link'
import { useRouter } from 'next/navigation'

export type Activity =
AppRouterOutput['groups']['activities']['list']['activities'][number]

type Props = {
groupId: string
activity: Activity
participant?: Participant
expense?: Awaited<ReturnType<typeof getGroupExpenses>>[number]
dateStyle: DateTimeStyle
}

Expand Down Expand Up @@ -44,13 +46,12 @@ export function ActivityItem({
groupId,
activity,
participant,
expense,
dateStyle,
}: Props) {
const router = useRouter()
const locale = useLocale()

const expenseExists = expense !== undefined
const expenseExists = activity.expense !== undefined
const summary = useSummary(activity, participant?.name)

return (
Expand Down
94 changes: 69 additions & 25 deletions src/app/groups/[groupId]/activity/activity-list.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { ActivityItem } from '@/app/groups/[groupId]/activity/activity-item'
import { getGroupExpenses } from '@/lib/api'
import { Activity, Participant } from '@prisma/client'
'use client'
import {
Activity,
ActivityItem,
} from '@/app/groups/[groupId]/activity/activity-item'
import { Skeleton } from '@/components/ui/skeleton'
import { trpc } from '@/trpc/client'
import dayjs, { type Dayjs } from 'dayjs'
import { useTranslations } from 'next-intl'
import { forwardRef, useEffect } from 'react'
import { useInView } from 'react-intersection-observer'

type Props = {
groupId: string
participants: Participant[]
expenses: Awaited<ReturnType<typeof getGroupExpenses>>
activities: Activity[]
}
const PAGE_SIZE = 20

const DATE_GROUPS = {
TODAY: 'today',
Expand Down Expand Up @@ -48,23 +49,64 @@ function getDateGroup(date: Dayjs, today: Dayjs) {
function getGroupedActivitiesByDate(activities: Activity[]) {
const today = dayjs()
return activities.reduce(
(result: { [key: string]: Activity[] }, activity: Activity) => {
(result, activity) => {
const activityGroup = getDateGroup(dayjs(activity.time), today)
result[activityGroup] = result[activityGroup] ?? []
result[activityGroup].push(activity)
return result
},
{},
{} as {
[key: string]: Activity[]
},
)
}

export function ActivityList({
groupId,
participants,
expenses,
activities,
}: Props) {
const ActivitiesLoading = forwardRef<HTMLDivElement>((_, ref) => {
return (
<div ref={ref} className="flex flex-col gap-4">
<Skeleton className="mt-2 h-3 w-24" />
{Array(5)
.fill(undefined)
.map((_, index) => (
<div key={index} className="flex gap-2 p-2">
<div className="flex-0">
<Skeleton className="h-3 w-12" />
</div>
<div className="flex-1">
<Skeleton className="h-3 w-48" />
</div>
</div>
))}
</div>
)
})
ActivitiesLoading.displayName = 'ActivitiesLoading'

export function ActivityList({ groupId }: { groupId: string }) {
const t = useTranslations('Activity')

const { data: groupData, isLoading: groupIsLoading } =
trpc.groups.get.useQuery({ groupId })

const {
data: activitiesData,
isLoading,
fetchNextPage,
} = trpc.groups.activities.list.useInfiniteQuery(
{ groupId, limit: PAGE_SIZE },
{ getNextPageParam: ({ nextCursor }) => nextCursor },
)
const { ref: loadingRef, inView } = useInView()

const activities = activitiesData?.pages.flatMap((page) => page.activities)
const hasMore = activitiesData?.pages.at(-1)?.hasMore ?? false

useEffect(() => {
if (inView && hasMore && !isLoading) fetchNextPage()
}, [fetchNextPage, hasMore, inView, isLoading])

if (isLoading || !activities || !groupData) return <ActivitiesLoading />

const groupedActivitiesByDate = getGroupedActivitiesByDate(activities)

return activities.length > 0 ? (
Expand All @@ -86,27 +128,29 @@ export function ActivityList({
>
{t(`Groups.${dateGroup}`)}
</div>
{groupActivities.map((activity: Activity) => {
{groupActivities.map((activity) => {
const participant =
activity.participantId !== null
? participants.find((p) => p.id === activity.participantId)
: undefined
const expense =
activity.expenseId !== null
? expenses.find((e) => e.id === activity.expenseId)
? groupData.group.participants.find(
(p) => p.id === activity.participantId,
)
: undefined
return (
<ActivityItem
key={activity.id}
{...{ groupId, activity, participant, expense, dateStyle }}
groupId={groupId}
activity={activity}
participant={participant}
dateStyle={dateStyle}
/>
)
})}
</div>
)
})}
{hasMore && <ActivitiesLoading ref={loadingRef} />}
</>
) : (
<p className="px-6 text-sm py-6">{t('noActivity')}</p>
<p className="text-sm py-6">{t('noActivity')}</p>
)
}
32 changes: 32 additions & 0 deletions src/app/groups/[groupId]/activity/page.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ActivityList } from '@/app/groups/[groupId]/activity/activity-list'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import { Metadata } from 'next'
import { useTranslations } from 'next-intl'

export const metadata: Metadata = {
title: 'Activity',
}

export function ActivityPageClient({ groupId }: { groupId: string }) {
const t = useTranslations('Activity')

return (
<>
<Card className="mb-4">
<CardHeader>
<CardTitle>{t('title')}</CardTitle>
<CardDescription>{t('description')}</CardDescription>
</CardHeader>
<CardContent className="flex flex-col space-y-4">
<ActivityList groupId={groupId} />
</CardContent>
</Card>
</>
)
}
41 changes: 2 additions & 39 deletions src/app/groups/[groupId]/activity/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
import { cached } from '@/app/cached-functions'
import { ActivityList } from '@/app/groups/[groupId]/activity/activity-list'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import { getActivities, getGroupExpenses } from '@/lib/api'
import { ActivityPageClient } from '@/app/groups/[groupId]/activity/page.client'
import { Metadata } from 'next'
import { getTranslations } from 'next-intl/server'
import { notFound } from 'next/navigation'

export const metadata: Metadata = {
title: 'Activity',
Expand All @@ -21,31 +10,5 @@ export default async function ActivityPage({
}: {
params: { groupId: string }
}) {
const t = await getTranslations('Activity')
const group = await cached.getGroup(groupId)
if (!group) notFound()

const expenses = await getGroupExpenses(groupId)
const activities = await getActivities(groupId)

return (
<>
<Card className="mb-4">
<CardHeader>
<CardTitle>{t('title')}</CardTitle>
<CardDescription>{t('description')}</CardDescription>
</CardHeader>
<CardContent className="flex flex-col space-y-4">
<ActivityList
{...{
groupId,
participants: group.participants,
expenses,
activities,
}}
/>
</CardContent>
</Card>
</>
)
return <ActivityPageClient groupId={groupId} />
}
2 changes: 1 addition & 1 deletion src/app/groups/[groupId]/expenses/expense-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { forwardRef, useEffect, useMemo, useState } from 'react'
import { useInView } from 'react-intersection-observer'
import { useDebounce } from 'use-debounce'

const PAGE_SIZE = 200
const PAGE_SIZE = 20

type ExpensesType = NonNullable<
Awaited<ReturnType<typeof getGroupExpensesAction>>
Expand Down
2 changes: 1 addition & 1 deletion src/app/groups/[groupId]/reimbursement-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function ReimbursementList({
const locale = useLocale()
const t = useTranslations('Balances.Reimbursements')
if (reimbursements.length === 0) {
return <p className="px-6 text-sm pb-6">{t('noImbursements')}</p>
return <p className="text-sm pb-6">{t('noImbursements')}</p>
}

const getParticipant = (id: string) => participants.find((p) => p.id === id)
Expand Down
27 changes: 25 additions & 2 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,11 +310,34 @@ export async function getExpense(groupId: string, expenseId: string) {
})
}

export async function getActivities(groupId: string) {
return prisma.activity.findMany({
export async function getActivities(
groupId: string,
options?: { offset?: number; length?: number },
) {
const activities = await prisma.activity.findMany({
where: { groupId },
orderBy: [{ time: 'desc' }],
skip: options?.offset,
take: options?.length,
})

const expenseIds = activities
.map((activity) => activity.expenseId)
.filter(Boolean)
const expenses = await prisma.expense.findMany({
where: {
groupId,
id: { in: expenseIds },
},
})

return activities.map((activity) => ({
...activity,
expense:
activity.expenseId !== null
? expenses.find((expense) => expense.id === activity.expenseId)
: undefined,
}))
}

export async function logActivity(
Expand Down
6 changes: 6 additions & 0 deletions src/trpc/routers/groups/activities/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createTRPCRouter } from '@/trpc/init'
import { listGroupActivitiesProcedure } from '@/trpc/routers/groups/activities/list.procedure'

export const activitiesRouter = createTRPCRouter({
list: listGroupActivitiesProcedure,
})
23 changes: 23 additions & 0 deletions src/trpc/routers/groups/activities/list.procedure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { getActivities } from '@/lib/api'
import { baseProcedure } from '@/trpc/init'
import { z } from 'zod'

export const listGroupActivitiesProcedure = baseProcedure
.input(
z.object({
groupId: z.string(),
cursor: z.number().optional().default(0),
limit: z.number().optional().default(5),
}),
)
.query(async ({ input: { groupId, cursor, limit } }) => {
const activities = await getActivities(groupId, {
offset: cursor,
length: limit + 1,
})
return {
activities: activities.slice(0, limit),
hasMore: !!activities[limit],
nextCursor: cursor + limit,
}
})
2 changes: 2 additions & 0 deletions src/trpc/routers/groups/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createTRPCRouter } from '@/trpc/init'
import { activitiesRouter } from '@/trpc/routers/groups/activities'
import { groupBalancesRouter } from '@/trpc/routers/groups/balances'
import { groupExpensesRouter } from '@/trpc/routers/groups/expenses'
import { getGroupProcedure } from '@/trpc/routers/groups/get.procedure'
Expand All @@ -9,6 +10,7 @@ export const groupsRouter = createTRPCRouter({
expenses: groupExpensesRouter,
balances: groupBalancesRouter,
stats: groupStatsRouter,
activities: activitiesRouter,

get: getGroupProcedure,
update: updateGroupProcedure,
Expand Down

0 comments on commit b1b2918

Please sign in to comment.