diff --git a/src/app/dashboard/(transactions)/transactions.tsx b/src/app/dashboard/(transactions)/transactions.tsx index 1f0be90..3505549 100644 --- a/src/app/dashboard/(transactions)/transactions.tsx +++ b/src/app/dashboard/(transactions)/transactions.tsx @@ -46,6 +46,7 @@ export default async function TransactionsOverview({ type, searchParams }: Trans {transactions.map((transaction) => { return ( Amount - e.target.select()} step={0.01} min={0.01} diff --git a/src/app/dashboard/charts/charts.tsx b/src/app/dashboard/charts/charts.tsx index 81ed4ed..67a0d2f 100644 --- a/src/app/dashboard/charts/charts.tsx +++ b/src/app/dashboard/charts/charts.tsx @@ -13,11 +13,11 @@ export default async function Charts({ month, year }: { month: string; year: str const user = await api.users.get.query(); const timezone = user?.timezone ?? 'Europe/Amsterdam'; - const time = parse(`${month}, ${year}`, 'LLLL, yyyy', new Date()); + const now = parse(`${month}, ${year}`, 'LLLL, yyyy', new Date()); const preferredTimezoneOffset = getTimezoneOffset(timezone); const localeTimezoneOffset = new Date().getTimezoneOffset() * 60 * 1000; - const from = new Date(startOfMonth(time).getTime() - preferredTimezoneOffset - localeTimezoneOffset); - const to = new Date(endOfMonth(time).getTime() - preferredTimezoneOffset - localeTimezoneOffset); + const from = new Date(startOfMonth(now).getTime() - preferredTimezoneOffset - localeTimezoneOffset); + const to = new Date(endOfMonth(now).getTime() - preferredTimezoneOffset - localeTimezoneOffset); const [expenses, incomes] = await Promise.all([ api.transactions.personal.period.query({ type: TransactionType.EXPENSE, from, to }), diff --git a/src/app/dashboard/register-transaction/register-personal-expense.tsx b/src/app/dashboard/register-transaction/register-personal-expense.tsx index c0bb3b2..da9637c 100644 --- a/src/app/dashboard/register-transaction/register-personal-expense.tsx +++ b/src/app/dashboard/register-transaction/register-personal-expense.tsx @@ -7,11 +7,12 @@ export default async function DashboardRegisterPersonalExpense() { api.tags.all.query({ type: TransactionType.EXPENSE }), api.users.get.query(), ]); - const weekStartsOn = user?.weekStartsOn ?? 1; - const timezone = user?.timezone ?? 'Europe/Amsterdam'; + const weekStartsOn = user.weekStartsOn ?? 1; + const timezone = user.timezone ?? 'Europe/Amsterdam'; return ( ({ ...t, text: t.name }))} diff --git a/src/app/dashboard/register-transaction/register-transaction.tsx b/src/app/dashboard/register-transaction/register-transaction.tsx index 5054ca9..5561f1c 100644 --- a/src/app/dashboard/register-transaction/register-transaction.tsx +++ b/src/app/dashboard/register-transaction/register-transaction.tsx @@ -3,6 +3,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import type { TransactionType } from '@prisma/client'; import { CalendarIcon } from '@radix-ui/react-icons'; +import currencySymbolMap from 'currency-symbol-map/map'; import { format } from 'date-fns'; import { zonedTimeToUtc } from 'date-fns-tz'; import { Loader2 } from 'lucide-react'; @@ -14,7 +15,7 @@ import { Button } from '~/components/ui/button'; import { Calendar } from '~/components/ui/calendar'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '~/components/ui/dialog'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '~/components/ui/form'; -import { Input } from '~/components/ui/input'; +import { Input, InputWithCurrency } from '~/components/ui/input'; import { Popover, PopoverContent, PopoverTrigger } from '~/components/ui/popover'; import { type Tag, TagInput } from '~/components/ui/tag-input/tag-input'; import { getRandomElement } from '~/lib/utils'; @@ -22,6 +23,7 @@ import { api } from '~/trpc/react'; import type { RouterOutputs } from '~/trpc/shared'; export type RegisterTransactionProps = { + currency: string | null; timezone: string; weekStartsOn: number; descriptions: string[]; @@ -30,6 +32,7 @@ export type RegisterTransactionProps = { }; export default function RegisterTransaction({ + currency, timezone, weekStartsOn, descriptions, @@ -125,8 +128,8 @@ export default function RegisterTransaction({ Amount - e.target.select()} step={0.01} min={0.01} diff --git a/src/app/groups/[groupId]/expenses/new/group-expense.client.tsx b/src/app/groups/[groupId]/expenses/new/group-expense.client.tsx index 366e5e7..babc382 100644 --- a/src/app/groups/[groupId]/expenses/new/group-expense.client.tsx +++ b/src/app/groups/[groupId]/expenses/new/group-expense.client.tsx @@ -4,6 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { AvatarIcon } from '@radix-ui/react-icons'; import currencySymbolMap from 'currency-symbol-map/map'; import { format } from 'date-fns'; +import { zonedTimeToUtc } from 'date-fns-tz'; import { CalendarIcon, ChevronRight, Loader2, Save, Trash2 } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; @@ -15,7 +16,7 @@ import { Button } from '~/components/ui/button'; import { Calendar } from '~/components/ui/calendar'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/components/ui/collapsible'; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '~/components/ui/form'; -import { Input } from '~/components/ui/input'; +import { Input, InputWithCurrency } from '~/components/ui/input'; import { Popover, PopoverContent, PopoverTrigger } from '~/components/ui/popover'; import { cn } from '~/lib/utils'; import { api } from '~/trpc/react'; @@ -100,6 +101,12 @@ export default function GroupExpenseForm({ group, user, expense }: GroupExpenseF }); } + if (user.timezone) { + // little hack to make sure the date used is timezoned to the user's preference + // the calendar component cannot be timezoned + data.date = zonedTimeToUtc(format(data.date, 'yyyy-MM-dd'), user.timezone ?? 'Europe/Amsterdam'); + } + upsert.mutate(data); } @@ -128,17 +135,13 @@ export default function GroupExpenseForm({ group, user, expense }: GroupExpenseF Amount -
- form.setValue('amount', parseFloat(e.target.value) || 0)} - /> -
-

{currencySymbolMap[user.currency ?? 'EUR']}

-
-
+ form.setValue('amount', parseFloat(e.target.value) || 0)} + />
How much was it? @@ -321,19 +324,13 @@ function SplitInput({ value, form, open, onOpenChange, title, group, user, onInp -
- s.userId === u.id)?.value ?? 0} - step={0.01} - min={0} - onChange={(e) => onInputChange(parseFloat(e.target.value), u)} - /> -
-

{currencySymbolMap[user.currency ?? 'EUR']}

-
-
+ s.userId === u.id)?.value ?? 0} + step={0.01} + min={0} + onChange={(e) => onInputChange(parseFloat(e.target.value), u)} + />
diff --git a/src/app/groups/[groupId]/group-charts.tsx b/src/app/groups/[groupId]/group-charts.tsx index ea70642..1a3fec3 100644 --- a/src/app/groups/[groupId]/group-charts.tsx +++ b/src/app/groups/[groupId]/group-charts.tsx @@ -19,11 +19,11 @@ export default async function GroupCharts({ const timezone = user.timezone ?? 'Europe/Amsterdam'; const users = group.UserGroup.map((e) => e.user); - const time = new Date(); + const now = new Date(); const preferredTimezoneOffset = getTimezoneOffset(timezone); const localeTimezoneOffset = new Date().getTimezoneOffset() * 60 * 1000; - const from = new Date(startOfMonth(time).getTime() - preferredTimezoneOffset - localeTimezoneOffset); - const to = new Date(endOfMonth(time).getTime() - preferredTimezoneOffset - localeTimezoneOffset); + const from = new Date(startOfMonth(now).getTime() - preferredTimezoneOffset - localeTimezoneOffset); + const to = new Date(endOfMonth(now).getTime() - preferredTimezoneOffset - localeTimezoneOffset); const [expenses, settlements] = await Promise.all([ api.groups.expenses.period.query({ groupId: group.id, from, to }), diff --git a/src/app/groups/[groupId]/register-settlement.client.tsx b/src/app/groups/[groupId]/register-settlement.client.tsx index a808d11..24d65fe 100644 --- a/src/app/groups/[groupId]/register-settlement.client.tsx +++ b/src/app/groups/[groupId]/register-settlement.client.tsx @@ -2,6 +2,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { AvatarIcon, CalendarIcon, CheckIcon } from '@radix-ui/react-icons'; +import currencySymbolMap from 'currency-symbol-map/map'; import { format } from 'date-fns'; import { zonedTimeToUtc } from 'date-fns-tz'; import { Loader2, MoveDown, Trash2 } from 'lucide-react'; @@ -16,7 +17,7 @@ import { Calendar } from '~/components/ui/calendar'; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '~/components/ui/command'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '~/components/ui/dialog'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '~/components/ui/form'; -import { Input } from '~/components/ui/input'; +import { InputWithCurrency } from '~/components/ui/input'; import { Popover, PopoverContent, PopoverTrigger } from '~/components/ui/popover'; import { cn } from '~/lib/utils'; import { api } from '~/trpc/react'; @@ -154,8 +155,8 @@ export default function RegisterSettlement({ group, user, children, settlement } Amount - e.target.select()} step={0.01} min={0.01} diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index 2d0180f..9e3d35f 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -20,4 +20,30 @@ const Input = React.forwardRef(({ className, type, }); Input.displayName = 'Input'; -export { Input }; +export interface InputWithCurrencyProps extends InputProps { + currency: string; +} + +const InputWithCurrency = React.forwardRef( + ({ className, currency, ...props }, ref) => { + return ( +
+ +
+

{currency}

+
+
+ ); + }, +); +InputWithCurrency.displayName = 'InputWithCurrency'; + +export { Input, InputWithCurrency }; diff --git a/src/server/api/routers/groups/groups.ts b/src/server/api/routers/groups/groups.ts index 00496dd..639d221 100644 --- a/src/server/api/routers/groups/groups.ts +++ b/src/server/api/routers/groups/groups.ts @@ -1,4 +1,3 @@ -import { settings } from '.eslintrc.cjs'; import { TRPCError } from '@trpc/server'; import { log } from 'next-axiom'; import { z } from 'zod';