Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/Duplicate expense #201

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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: 7 additions & 0 deletions messages/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@
"yes": "Ja",
"cancel": "Abbrechen"
},
"ClonePopup": {
"label": "Clone",
"title": "Clone this expense?",
"description": "Do you really want to clone this expense?",
"yes": "Yes",
"cancel": "Cancel"
},
"attachDocuments": "Dokument hinzufügen",
"create": "Erstellen",
"creating": "Erstellt…",
Expand Down
7 changes: 7 additions & 0 deletions messages/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@
"yes": "Yes",
"cancel": "Cancel"
},
"ClonePopup": {
"label": "Clone",
"title": "Clone this expense?",
"description": "Do you really want to clone this expense?",
"yes": "Yes",
"cancel": "Cancel"
},
"attachDocuments": "Attach documents",
"create": "Create",
"creating": "Creating…",
Expand Down
7 changes: 7 additions & 0 deletions messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@
"yes": "Si",
"cancel": "Cancelar"
},
"ClonePopup": {
"label": "Clone",
"title": "Clone this expense?",
"description": "Do you really want to clone this expense?",
"yes": "Yes",
"cancel": "Cancel"
},
"attachDocuments": "Adjuntar documentos",
"create": "Crear",
"creating": "Creando",
Expand Down
7 changes: 7 additions & 0 deletions messages/fi.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@
"yes": "Kyllä",
"cancel": "Peruuta"
},
"ClonePopup": {
"label": "Clone",
"title": "Clone this expense?",
"description": "Do you really want to clone this expense?",
"yes": "Kyllä",
"cancel": "Peruuta"
},
"attachDocuments": "Liitä dokumenttejä",
"create": "Lisää kulu",
"creating": "Luodaan kulua…",
Expand Down
7 changes: 7 additions & 0 deletions messages/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@
"yes": "Oui",
"cancel": "Annuler"
},
"ClonePopup": {
"label": "Clone",
"title": "Clone this expense?",
"description": "Do you really want to clone this expense?",
"yes": "Yes",
"cancel": "Cancel"
},
"attachDocuments": "Joindre des documents",
"create": "Créer",
"creating": "Création…",
Expand Down
7 changes: 7 additions & 0 deletions messages/it-IT.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@
"yes": "Si",
"cancel": "Annulla"
},
"ClonePopup": {
"label": "Clone",
"title": "Clone this expense?",
"description": "Do you really want to clone this expense?",
"yes": "Yes",
"cancel": "Cancel"
},
"attachDocuments": "Documenti allegati",
"create": "Crea",
"creating": "Sto creando…",
Expand Down
7 changes: 7 additions & 0 deletions messages/ru-RU.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@
"yes": "Удалить",
"cancel": "Отмена"
},
"ClonePopup": {
"label": "Clone",
"title": "Clone this expense?",
"description": "Do you really want to clone this expense?",
"yes": "Yes",
"cancel": "Cancel"
},
"attachDocuments": "Прикрепить документы",
"create": "Создать",
"creating": "Создание…",
Expand Down
7 changes: 7 additions & 0 deletions messages/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@
"yes": "确定",
"cancel": "取消"
},
"ClonePopup": {
"label": "Clone",
"title": "Clone this expense?",
"description": "Do you really want to clone this expense?",
"yes": "Yes",
"cancel": "Cancel"
},
"attachDocuments": "附加文档",
"create": "创建",
"creating": "创建中……",
Expand Down
7 changes: 7 additions & 0 deletions src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { cached } from '@/app/cached-functions'
import { ExpenseForm } from '@/components/expense-form'
import {
cloneExpense,
deleteExpense,
getCategories,
getExpense,
Expand Down Expand Up @@ -39,6 +40,11 @@ export default async function EditExpensePage({
await deleteExpense(groupId, expenseId, participantId)
redirect(`/groups/${groupId}`)
}
async function cloneExpenseAction(participantId?: string) {
'use server'
await cloneExpense(groupId, expenseId, participantId)
redirect(`/groups/${groupId}`)
}

return (
<Suspense>
Expand All @@ -48,6 +54,7 @@ export default async function EditExpensePage({
categories={categories}
onSubmit={updateExpenseAction}
onDelete={deleteExpenseAction}
onClone={cloneExpenseAction}
runtimeFeatureFlags={await getRuntimeFeatureFlags()}
/>
</Suspense>
Expand Down
46 changes: 46 additions & 0 deletions src/components/clone-popup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use client'

import { Copy } from 'lucide-react'
import { useTranslations } from 'next-intl'
import { AsyncButton } from './async-button'
import { Button } from './ui/button'
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogTitle,
DialogTrigger,
} from './ui/dialog'

export function ClonePopup({ onClone }: { onClone: () => Promise<void> }) {
const t = useTranslations('ExpenseForm.ClonePopup')
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">
<Copy className="w-4 h-4 mr-2" />
{t('label')}
</Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>{t('title')}</DialogTitle>
<DialogDescription>{t('description')}</DialogDescription>
<DialogFooter className="flex flex-col gap-2">
<AsyncButton
type="button"
variant="destructive"
loadingContent="Cloning…"
action={onClone}
>
{t('yes')}
</AsyncButton>
<DialogClose asChild>
<Button variant={'secondary'}>{t('cancel')}</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
8 changes: 8 additions & 0 deletions src/components/expense-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { useSearchParams } from 'next/navigation'
import { useEffect, useState } from 'react'
import { useForm } from 'react-hook-form'
import { match } from 'ts-pattern'
import { ClonePopup } from './clone-popup'
import { DeletePopup } from './delete-popup'
import { extractCategoryFromTitle } from './expense-form-actions'
import { Textarea } from './ui/textarea'
Expand All @@ -60,6 +61,7 @@ export type Props = {
categories: NonNullable<Awaited<ReturnType<typeof getCategories>>>
onSubmit: (values: ExpenseFormValues, participantId?: string) => Promise<void>
onDelete?: (participantId?: string) => Promise<void>
onClone?: (participantId?: string) => Promise<void>
runtimeFeatureFlags: RuntimeFeatureFlags
}

Expand Down Expand Up @@ -150,6 +152,7 @@ export function ExpenseForm({
categories,
onSubmit,
onDelete,
onClone,
runtimeFeatureFlags,
}: Props) {
const t = useTranslations('ExpenseForm')
Expand Down Expand Up @@ -816,6 +819,11 @@ export function ExpenseForm({
onDelete={() => onDelete(activeUserId ?? undefined)}
></DeletePopup>
)}
{!isCreate && onClone && (
<ClonePopup
onClone={() => onClone(activeUserId ?? undefined)}
></ClonePopup>
)}
<Button variant="ghost" asChild>
<Link href={`/groups/${group.id}`}>{t('cancel')}</Link>
</Button>
Expand Down
38 changes: 38 additions & 0 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { prisma } from '@/lib/prisma'
import { ExpenseFormValues, GroupFormValues } from '@/lib/schemas'
import { ActivityType, Expense } from '@prisma/client'
import { nanoid } from 'nanoid'
import { notFound } from 'next/navigation'

export function randomId() {
return nanoid()
Expand Down Expand Up @@ -84,6 +85,43 @@ export async function createExpense(
})
}

export async function cloneExpense(
groupId: string,
expenseId: string,
participantId?: string,
) {
const group = await getGroup(groupId)
if (!group) notFound()

const expense = await getExpense(groupId, expenseId)
if (!expense) notFound()

const paidForCloned = []
for (const participant of expense.paidFor) {
const participantObject = {
participant: participant.participantId,
shares: participant.shares,
}
paidForCloned.push(participantObject)
}

const formClone: ExpenseFormValues = {
expenseDate: new Date(),
category: expense.categoryId,
amount: expense.amount,
title: expense.title + ' clone',
paidBy: expense.paidBy.id,
splitMode: expense.splitMode,
paidFor: paidForCloned,
isReimbursement: expense.isReimbursement,
documents: expense.documents,
notes: expense.notes ? expense.notes : undefined,
saveDefaultSplittingOptions: false,
}

await createExpense(formClone, groupId, participantId)
}

export async function deleteExpense(
groupId: string,
expenseId: string,
Expand Down
Loading