Skip to content

Commit

Permalink
add edit expense (#150)
Browse files Browse the repository at this point in the history
* add edit expense

* add edit expense changes

* fix build

* fix edit not working on exact match

* set share to zero

* fix deleting user on edit not working

* fix auto load to expense page

* make split share nice
  • Loading branch information
KMKoushik authored Dec 3, 2024
1 parent e00a6b2 commit b4a953f
Show file tree
Hide file tree
Showing 16 changed files with 747 additions and 189 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Expense" ADD COLUMN "updatedBy" INTEGER;

-- AddForeignKey
ALTER TABLE "Expense" ADD CONSTRAINT "Expense_updatedBy_fkey" FOREIGN KEY ("updatedBy") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
3 changes: 3 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ model User {
paidExpenses Expense[] @relation("PaidByUser")
addedExpenses Expense[] @relation("AddedByUser")
deletedExpenses Expense[] @relation("DeletedByUser")
updatedExpenses Expense[] @relation("UpdatedByUser")
}

model VerificationToken {
Expand Down Expand Up @@ -149,10 +150,12 @@ model Expense {
groupId Int?
deletedAt DateTime?
deletedBy Int?
updatedBy Int?
group Group? @relation(fields: [groupId], references: [id], onDelete: Cascade)
paidByUser User @relation(name: "PaidByUser", fields: [paidBy], references: [id], onDelete: Cascade)
addedByUser User @relation(name: "AddedByUser", fields: [addedBy], references: [id], onDelete: Cascade)
deletedByUser User? @relation(name: "DeletedByUser", fields: [deletedBy], references: [id], onDelete: Cascade)
updatedByUser User? @relation(name: "UpdatedByUser", fields: [updatedBy], references: [id], onDelete: SetNull)
expenseParticipants ExpenseParticipant[]
expenseNotes ExpenseNote[]
Expand Down
40 changes: 27 additions & 13 deletions src/components/AddExpense/AddExpensePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,13 @@ const categories = {
},
};

export const AddExpensePage: React.FC<{
export const AddOrEditExpensePage: React.FC<{
isStorageConfigured: boolean;
enableSendingInvites: boolean;
}> = ({ isStorageConfigured, enableSendingInvites }) => {
expenseId?: string;
}> = ({ isStorageConfigured, enableSendingInvites, expenseId }) => {
const [date, setDate] = React.useState<Date | undefined>(new Date());
const [open, setOpen] = React.useState(false);
const [amtStr, setAmountStr] = React.useState('');

const showFriends = useAddExpenseStore((s) => s.showFriends);
const amount = useAddExpenseStore((s) => s.amount);
Expand All @@ -122,13 +122,20 @@ export const AddExpensePage: React.FC<{
const category = useAddExpenseStore((s) => s.category);
const description = useAddExpenseStore((s) => s.description);
const isFileUploading = useAddExpenseStore((s) => s.isFileUploading);
const amtStr = useAddExpenseStore((s) => s.amountStr);

const { setCurrency, setCategory, setDescription, setAmount, resetState } = useAddExpenseStore(
(s) => s.actions,
);
const {
setCurrency,
setCategory,
setDescription,
setAmount,
setAmountStr,
resetState,
setSplitScreenOpen,
} = useAddExpenseStore((s) => s.actions);

const addExpenseMutation = api.user.addExpense.useMutation();
const addGroupExpenseMutation = api.group.addExpense.useMutation();
const addExpenseMutation = api.user.addOrEditExpense.useMutation();
const addGroupExpenseMutation = api.group.addOrEditExpense.useMutation();
const updateProfile = api.user.updateUserDetail.useMutation();

const router = useRouter();
Expand All @@ -140,11 +147,17 @@ export const AddExpensePage: React.FC<{
}

function addExpense() {
const { group, paidBy, splitType, fileKey } = useAddExpenseStore.getState();
const { group, paidBy, splitType, fileKey, canSplitScreenClosed } =
useAddExpenseStore.getState();
if (!paidBy) {
return;
}

if (!canSplitScreenClosed) {
setSplitScreenOpen(true);
return;
}

if (group) {
addGroupExpenseMutation.mutate(
{
Expand All @@ -161,12 +174,13 @@ export const AddExpensePage: React.FC<{
category,
fileKey,
expenseDate: date,
expenseId,
},
{
onSuccess: (d) => {
if (d) {
router
.push(`/groups/${group.id}/expenses/${d?.id}`)
.push(`/groups/${group.id}/expenses/${d?.id ?? expenseId}`)
.then(() => resetState())
.catch(console.error);
}
Expand All @@ -176,6 +190,7 @@ export const AddExpensePage: React.FC<{
} else {
addExpenseMutation.mutate(
{
expenseId,
name: description,
currency,
amount,
Expand All @@ -191,10 +206,9 @@ export const AddExpensePage: React.FC<{
},
{
onSuccess: (d) => {
resetState();
if (participants[1] && d) {
router
.push(`/balances/${participants[1]?.id}/expenses/${d?.id}`)
.push(`expenses/${d?.id ?? expenseId}`)
.then(() => resetState())
.catch(console.error);
}
Expand Down Expand Up @@ -237,7 +251,7 @@ export const AddExpensePage: React.FC<{
Save
</Button>{' '}
</div>
<UserInput />
<UserInput isEditing={!!expenseId} />
{showFriends || (participants.length === 1 && !group) ? (
<SelectUserOrGroup enableSendingInvites={enableSendingInvites} />
) : (
Expand Down
5 changes: 4 additions & 1 deletion src/components/AddExpense/SplitTypeSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ export const SplitTypeSection: React.FC = () => {
const currentUser = useAddExpenseStore((s) => s.currentUser);
const canSplitScreenClosed = useAddExpenseStore((s) => s.canSplitScreenClosed);
const splitType = useAddExpenseStore((s) => s.splitType);
const splitScreenOpen = useAddExpenseStore((s) => s.splitScreenOpen);

const { setPaidBy } = useAddExpenseStore((s) => s.actions);
const { setPaidBy, setSplitScreenOpen } = useAddExpenseStore((s) => s.actions);

return (
<div className="mt-4 flex items-center justify-center text-[16px] text-gray-400">
Expand Down Expand Up @@ -63,6 +64,8 @@ export const SplitTypeSection: React.FC = () => {
dismissible={false}
actionTitle="Save"
actionDisabled={!canSplitScreenClosed}
open={splitScreenOpen}
onOpenChange={(open) => setSplitScreenOpen(open)}
>
<SplitExpenseForm />
</AppDrawer>
Expand Down
17 changes: 11 additions & 6 deletions src/components/AddExpense/UserInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { z } from 'zod';
import { api } from '~/utils/api';
import Router from 'next/router';

export const UserInput: React.FC = () => {
export const UserInput: React.FC<{
isEditing?: boolean;
}> = ({ isEditing }) => {
const {
setNameOrEmail,
removeLastParticipant,
Expand Down Expand Up @@ -85,17 +87,20 @@ export const UserInput: React.FC = () => {
<input
type="email"
placeholder={
group
? 'Press delete to remove group'
: participants.length > 1
? 'Add more friends'
: 'Search friends, groups or add email'
isEditing && !!group
? 'Cannot change group while editing'
: group
? 'Press delete to remove group'
: participants.length > 1
? 'Add more friends'
: 'Search friends, groups or add email'
}
value={nameOrEmail}
onChange={(e) => setNameOrEmail(e.target.value)}
onKeyDown={handleKeyDown}
className="min-w-[100px] flex-grow bg-transparent outline-none placeholder:text-sm focus:ring-0"
autoFocus
disabled={isEditing && !!group}
/>
</div>
);
Expand Down
7 changes: 7 additions & 0 deletions src/components/Expense/ExpensePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type ExpenseDetailsProps = {
addedByUser: User;
paidByUser: User;
deletedByUser: User | null;
updatedByUser: User | null;
};
storagePublicUrl?: string;
};
Expand All @@ -42,6 +43,12 @@ const ExpenseDetails: React.FC<ExpenseDetailsProps> = ({ user, expense, storageP
{!isSameDay(expense.expenseDate, expense.createdAt) ? (
<p className="text-sm text-gray-500">{format(expense.expenseDate, 'dd MMM yyyy')}</p>
) : null}
{expense.updatedByUser ? (
<p className=" text-sm text-gray-500">
Edited by {expense.updatedByUser?.name ?? expense.updatedByUser?.email} on{' '}
{format(expense.updatedAt, 'dd MMM yyyy')}
</p>
) : null}
{expense.deletedByUser ? (
<p className=" text-sm text-orange-600">
Deleted by {expense.deletedByUser.name ?? expense.addedByUser.email} on{' '}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Friend/Settleup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const SettleUp: React.FC<{
setAmount(toFixedNumber(Math.abs(balance.amount)).toString());
}

const addExpenseMutation = api.user.addExpense.useMutation();
const addExpenseMutation = api.user.addOrEditExpense.useMutation();
const utils = api.useUtils();

function saveExpense() {
Expand Down
74 changes: 63 additions & 11 deletions src/pages/add.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
import Head from 'next/head';
import { useRouter } from 'next/router';
import React, { useEffect } from 'react';
import { AddExpensePage } from '~/components/AddExpense/AddExpensePage';
import { AddOrEditExpensePage } from '~/components/AddExpense/AddExpensePage';
import MainLayout from '~/components/Layout/MainLayout';
import { env } from '~/env';
import { isStorageConfigured } from '~/server/storage';
import { useAddExpenseStore } from '~/store/addStore';
import { calculateSplitShareBasedOnAmount, useAddExpenseStore } from '~/store/addStore';
import { type NextPageWithUser } from '~/types';
import { api } from '~/utils/api';
import { toFixedNumber, toInteger } from '~/utils/numbers';

// 🧾

const AddPage: NextPageWithUser<{
isStorageConfigured: boolean;
enableSendingInvites: boolean;
}> = ({ user, isStorageConfigured, enableSendingInvites }) => {
const { setCurrentUser, setGroup, setParticipants, setCurrency } = useAddExpenseStore(
(s) => s.actions,
);
const {
setCurrentUser,
setGroup,
setParticipants,
setCurrency,
setAmount,
setDescription,
setPaidBy,
setAmountStr,
setSplitType,
} = useAddExpenseStore((s) => s.actions);
const currentUser = useAddExpenseStore((s) => s.currentUser);

useEffect(() => {
Expand All @@ -32,11 +41,11 @@ const AddPage: NextPageWithUser<{
}, []);

const router = useRouter();
const { friendId, groupId } = router.query;
const { friendId, groupId, expenseId } = router.query;

const _groupId = parseInt(groupId as string);
const _friendId = parseInt(friendId as string);

const _expenseId = expenseId as string;
const groupQuery = api.group.getGroupDetails.useQuery(
{ groupId: _groupId },
{ enabled: !!_groupId },
Expand All @@ -47,6 +56,11 @@ const AddPage: NextPageWithUser<{
{ enabled: !!_friendId },
);

const expenseQuery = api.user.getExpenseDetails.useQuery(
{ expenseId: _expenseId },
{ enabled: !!_expenseId, refetchOnWindowFocus: false },
);

useEffect(() => {
// Set group
if (groupId && !groupQuery.isLoading && groupQuery.data && currentUser) {
Expand All @@ -69,16 +83,56 @@ const AddPage: NextPageWithUser<{
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [friendId, friendQuery.isLoading, friendQuery.data, currentUser]);

useEffect(() => {
if (_expenseId && expenseQuery.data) {
console.log(
'expenseQuery.data 123',
expenseQuery.data.expenseParticipants,
expenseQuery.data.splitType,
calculateSplitShareBasedOnAmount(
toFixedNumber(expenseQuery.data.amount),
expenseQuery.data.expenseParticipants.map((ep) => ({
...ep.user,
amount: toFixedNumber(ep.amount),
})),
expenseQuery.data.splitType,
expenseQuery.data.paidByUser,
),
);
expenseQuery.data.group && setGroup(expenseQuery.data.group);
setParticipants(
calculateSplitShareBasedOnAmount(
toFixedNumber(expenseQuery.data.amount),
expenseQuery.data.expenseParticipants.map((ep) => ({
...ep.user,
amount: toFixedNumber(ep.amount),
})),
expenseQuery.data.splitType,
expenseQuery.data.paidByUser,
),
);
setCurrency(expenseQuery.data.currency);
setAmountStr(toFixedNumber(expenseQuery.data.amount).toString());
setDescription(expenseQuery.data.name);
setPaidBy(expenseQuery.data.paidByUser);
setAmount(toFixedNumber(expenseQuery.data.amount));
setSplitType(expenseQuery.data.splitType);
useAddExpenseStore.setState({ showFriends: false });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [_expenseId, expenseQuery.data]);

return (
<>
<Head>
<title>Add Expense</title>
</Head>
<MainLayout hideAppBar>
{currentUser ? (
<AddExpensePage
{currentUser && (!_expenseId || expenseQuery.data) ? (
<AddOrEditExpensePage
isStorageConfigured={isStorageConfigured}
enableSendingInvites={enableSendingInvites}
expenseId={_expenseId}
/>
) : (
<div></div>
Expand All @@ -93,8 +147,6 @@ AddPage.auth = true;
export default AddPage;

export async function getServerSideProps() {
console.log('isStorageConfigured', isStorageConfigured());

return {
props: {
isStorageConfigured: !!isStorageConfigured(),
Expand Down
20 changes: 14 additions & 6 deletions src/pages/balances/[friendId]/expenses/[expenseId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import MainLayout from '~/components/Layout/MainLayout';
import { api } from '~/utils/api';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { ChevronLeftIcon, Trash, Trash2 } from 'lucide-react';
import { ChevronLeftIcon, PencilIcon, Trash, Trash2 } from 'lucide-react';
import ExpenseDetails from '~/components/Expense/ExpensePage';
import { DeleteExpense } from '~/components/Expense/DeleteExpense';
import { type NextPageWithUser } from '~/types';
import { env } from '~/env';
import { Button } from '~/components/ui/button';

const ExpensesPage: NextPageWithUser<{ storagePublicUrl?: string }> = ({
user,
Expand Down Expand Up @@ -35,11 +36,18 @@ const ExpensesPage: NextPageWithUser<{ storagePublicUrl?: string }> = ({
</div>
}
actions={
<DeleteExpense
expenseId={expenseId}
friendId={friendId}
groupId={expenseQuery.data?.groupId ?? undefined}
/>
<div className="flex items-center gap-1">
<DeleteExpense
expenseId={expenseId}
friendId={friendId}
groupId={expenseQuery.data?.groupId ?? undefined}
/>
<Link href={`/add?expenseId=${expenseId}`}>
<Button variant="ghost">
<PencilIcon className="mr-1 h-4 w-4" />
</Button>
</Link>
</div>
}
>
{expenseQuery.data ? (
Expand Down
Loading

0 comments on commit b4a953f

Please sign in to comment.