Skip to content

Commit

Permalink
feat: new detail view
Browse files Browse the repository at this point in the history
  • Loading branch information
kdurek committed Feb 3, 2024
1 parent c20f022 commit bedab22
Show file tree
Hide file tree
Showing 15 changed files with 224 additions and 70 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"superjson": "2.2.1",
"tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.0",
"zod": "^3.22.4"
},
"devDependencies": {
Expand Down
17 changes: 17 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion src/app/(app)/(expenses)/_components/expense-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export function ExpenseDetail({ expense, session }: ExpenseDetailProps) {
return (
<div className="space-y-6">
<div className="space-y-2">
<Heading variant="h1">{expense.name}</Heading>
<ExpenseDescriptionCard description={expense.description} />
<div>{formattedDate}</div>
</div>
Expand Down Expand Up @@ -85,7 +86,10 @@ export function ExpenseDetail({ expense, session }: ExpenseDetailProps) {
<div className="space-y-2">
<Heading variant="h2">Ustawienia</Heading>
<div className="flex gap-2">
<Link href={`/wydatki/${expense.id}/edytuj`} className={cn(buttonVariants({ variant: 'outline' }))}>
<Link
href={`/wydatki/${expense.id}/edytuj`}
className={cn(buttonVariants({ variant: 'outline' }), 'w-full')}
>
Edytuj
</Link>
<ExpenseDeleteModal expenseId={expense.id} />
Expand Down
9 changes: 7 additions & 2 deletions src/app/(app)/(expenses)/_components/expenses-archive.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
'use client';

import type { Session } from 'next-auth';
import React, { useEffect, useRef } from 'react';
import { useIntersection } from 'react-use';

import { ExpensesList, ExpensesListSkeleton } from '@/app/(app)/(expenses)/_components/expenses-list';
import { api } from '@/trpc/react';

export function ExpensesArchive() {
interface ExpensesArchiveProps {
session: Session;
}

export function ExpensesArchive({ session }: ExpensesArchiveProps) {
const intersectionRef = useRef(null);
const intersection = useIntersection(intersectionRef, {
root: null,
Expand Down Expand Up @@ -37,7 +42,7 @@ export function ExpensesArchive() {

return (
<>
<ExpensesList expenses={expenses} />
<ExpensesList expenses={expenses} session={session} />
<div ref={intersectionRef} />
</>
);
Expand Down
9 changes: 7 additions & 2 deletions src/app/(app)/(expenses)/_components/expenses-dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
'use client';

import type { Session } from 'next-auth';
import { useEffect, useRef } from 'react';
import { useIntersection } from 'react-use';

import { ExpensesList, ExpensesListSkeleton } from '@/app/(app)/(expenses)/_components/expenses-list';
import { api } from '@/trpc/react';

export function ExpensesDashboard() {
interface ExpensesDashboardProps {
session: Session;
}

export function ExpensesDashboard({ session }: ExpensesDashboardProps) {
const intersectionRef = useRef(null);
const intersection = useIntersection(intersectionRef, {
root: null,
Expand Down Expand Up @@ -37,7 +42,7 @@ export function ExpensesDashboard() {

return (
<>
<ExpensesList expenses={expenses} />
<ExpensesList expenses={expenses} session={session} />
<div ref={intersectionRef} />
</>
);
Expand Down
53 changes: 29 additions & 24 deletions src/app/(app)/(expenses)/_components/expenses-list-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import { type Prisma } from '@prisma/client';
import { format } from 'date-fns';
import { CircleDollarSign } from 'lucide-react';
import { useRouter } from 'next/navigation';
import type { Session } from 'next-auth';

import { Drawer, DrawerContent, DrawerTrigger } from '@/app/_components/ui/drawer';
import { Skeleton } from '@/app/_components/ui/skeleton';
import { ExpenseDetail } from '@/app/(app)/(expenses)/_components/expense-detail';
import { cn } from '@/lib/utils';
import type { ExpensesGetArchived, ExpensesGetDashboard } from '@/trpc/shared';

type ExpenseWithDebts = Prisma.ExpenseGetPayload<{
include: {
Expand Down Expand Up @@ -50,35 +53,37 @@ function ExpensesListCardIcon({ status }: ExpensesListCardIconProps) {
}

interface ExpensesListCardProps {
id: string;
status: ReturnType<typeof getExpenseStatus>;
name: string;
amount: number;
date: Date;
expense: ExpensesGetDashboard['items'][number] | ExpensesGetArchived['items'][number];
session: Session;
}

export function ExpensesListCard({ id, status, name, amount, date }: ExpensesListCardProps) {
const router = useRouter();

const formattedDate = format(date, 'EEEEEE, d MMMM');

function handleClick() {
router.push(`/wydatki/${id}`);
}
export function ExpensesListCard({ expense, session }: ExpensesListCardProps) {
const formattedDate = format(expense.createdAt, 'EEEEEE, d MMMM');

return (
<button onClick={handleClick} className="w-full py-4">
<div className="flex items-center justify-between overflow-hidden">
<div className="flex items-center gap-4">
<ExpensesListCardIcon status={status} />
<div className="overflow-hidden text-start">
<div className="line-clamp-1">{name}</div>
<div className="line-clamp-1 text-sm text-muted-foreground">{formattedDate}</div>
<Drawer>
<DrawerTrigger asChild>
<button className="w-full py-4">
<div className="flex items-center justify-between overflow-hidden">
<div className="flex items-center gap-4">
<ExpensesListCardIcon status={getExpenseStatus(expense)} />
<div className="overflow-hidden text-start">
<div className="line-clamp-1">{expense.name}</div>
<div className="line-clamp-1 text-sm text-muted-foreground">{formattedDate}</div>
</div>
</div>
<div className="whitespace-nowrap text-sm text-muted-foreground">
{Number(expense.amount).toFixed(2)}
</div>
</div>
</button>
</DrawerTrigger>
<DrawerContent className="max-h-[96%]">
<div className="overflow-auto p-4">
<ExpenseDetail expense={expense} session={session} />
</div>
<div className="whitespace-nowrap text-sm text-muted-foreground">{amount.toFixed(2)}</div>
</div>
</button>
</DrawerContent>
</Drawer>
);
}

Expand Down
28 changes: 7 additions & 21 deletions src/app/(app)/(expenses)/_components/expenses-list.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,20 @@
'use client';

import { type Prisma } from '@prisma/client';
import type { Session } from 'next-auth';

import {
ExpensesListCard,
ExpensesListCardSkeleton,
getExpenseStatus,
} from '@/app/(app)/(expenses)/_components/expenses-list-card';
import { ExpensesListCard, ExpensesListCardSkeleton } from '@/app/(app)/(expenses)/_components/expenses-list-card';
import type { ExpensesGetArchived, ExpensesGetDashboard } from '@/trpc/shared';

type ExpenseWithDebts = Prisma.ExpenseGetPayload<{
include: {
debts: true;
};
}>;
interface ExpenseListProps {
expenses: ExpenseWithDebts[];
expenses: ExpensesGetDashboard['items'] | ExpensesGetArchived['items'];
session: Session;
}

export function ExpensesList({ expenses }: ExpenseListProps) {
export function ExpensesList({ expenses, session }: ExpenseListProps) {
return (
<div className="divide-y">
{expenses.map((expense) => (
<ExpensesListCard
key={expense.id}
id={expense.id}
status={getExpenseStatus(expense)}
name={expense.name}
amount={Number(expense.amount)}
date={expense.createdAt}
/>
<ExpensesListCard key={expense.id} expense={expense} session={session} />
))}
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/app/(app)/(expenses)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default async function ExpensesPage() {
<Section title="Wydatki">
<UserStats session={session} />
<Suspense fallback={<ExpensesDashboardSkeleton />}>
<ExpensesDashboard />
<ExpensesDashboard session={session} />
</Suspense>
<Link href="/wydatki/archiwum" className={cn(buttonVariants({ variant: 'outline' }), 'w-full')}>
<Archive className="mr-2 size-4" />
Expand Down
2 changes: 1 addition & 1 deletion src/app/(app)/(expenses)/wydatki/[expenseId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default async function ExpensePage({ params }: ExpensePageProps) {
}

return (
<Section title={expense.name}>
<Section>
<ExpenseDetail expense={expense} session={session} />
</Section>
);
Expand Down
2 changes: 1 addition & 1 deletion src/app/(app)/(expenses)/wydatki/archiwum/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default async function ArchivePage() {
return (
<Section title="Archiwum">
<Suspense fallback={<ExpensesArchiveSkeleton />}>
<ExpensesArchive />
<ExpensesArchive session={session} />
</Suspense>
</Section>
);
Expand Down
4 changes: 2 additions & 2 deletions src/app/(app)/(expenses)/wydatki/uzytkownik/[userId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ export default async function ExpenseDetailsPage({ params }: ExpenseDetailsPageP
<TabsTrigger value="debts">Pożyczyłeś</TabsTrigger>
</TabsList>
<TabsContent value="credits">
<ExpensesList expenses={credits} />
<ExpensesList expenses={credits} session={session} />
</TabsContent>
<TabsContent value="debts">
<ExpensesList expenses={debts} />
<ExpensesList expenses={debts} session={session} />
</TabsContent>
</Tabs>
</Section>
Expand Down
11 changes: 7 additions & 4 deletions src/app/_components/layout/section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type ReactNode, Suspense } from 'react';

import FullScreenLoading from '@/app/_components/layout/loading';
import { Heading } from '@/app/_components/ui/heading';
import { cn } from '@/lib/utils';

interface SectionProps {
children: ReactNode;
Expand All @@ -11,10 +12,12 @@ interface SectionProps {
export function Section({ title, children }: SectionProps) {
return (
<div>
<Heading variant="h1" className="p-4">
{title}
</Heading>
<section className="p-4 pt-0">
{title && (
<Heading variant="h1" className="p-4">
{title}
</Heading>
)}
<section className={cn('p-4', title && 'pt-0')}>
<Suspense fallback={<FullScreenLoading />}>{children}</Suspense>
</section>
</div>
Expand Down
89 changes: 89 additions & 0 deletions src/app/_components/ui/drawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use client';

import * as React from 'react';
import { Drawer as DrawerPrimitive } from 'vaul';

import { cn } from '@/lib/utils';

const Drawer = ({ shouldScaleBackground = true, ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerPrimitive.Root shouldScaleBackground={shouldScaleBackground} {...props} />
);
Drawer.displayName = 'Drawer';

const DrawerTrigger = DrawerPrimitive.Trigger;

const DrawerPortal = DrawerPrimitive.Portal;

const DrawerClose = DrawerPrimitive.Close;

const DrawerOverlay = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay ref={ref} className={cn('fixed inset-0 z-50 bg-black/80', className)} {...props} />
));
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;

const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
ref={ref}
className={cn(
'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background',
className,
)}
{...props}
>
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
));
DrawerContent.displayName = 'DrawerContent';

const DrawerHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn('grid gap-1.5 p-4 text-center sm:text-left', className)} {...props} />
);
DrawerHeader.displayName = 'DrawerHeader';

const DrawerFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn('mt-auto flex flex-col gap-2 p-4', className)} {...props} />
);
DrawerFooter.displayName = 'DrawerFooter';

const DrawerTitle = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Title
ref={ref}
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
{...props}
/>
));
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;

const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Description ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
));
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;

export {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerOverlay,
DrawerPortal,
DrawerTitle,
DrawerTrigger,
};
Loading

0 comments on commit bedab22

Please sign in to comment.