diff --git a/src/app/dashboard/file/file-processing.tsx b/src/app/dashboard/file/file-processing.tsx new file mode 100644 index 0000000..11e9ca9 --- /dev/null +++ b/src/app/dashboard/file/file-processing.tsx @@ -0,0 +1,279 @@ +'use client'; + +import { format, parse as parseDate } from 'date-fns'; +import { useRef, useState } from 'react'; +import { BlockTitle, BlockBody } from '~/app/_components/block'; +import { Input } from '~/components/ui/input'; +import { Label } from '~/components/ui/label'; +import { parse as parseCSV } from 'csv-parse'; +import { cn } from '~/lib/utils.client'; +import { Button } from '~/components/ui/button'; +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '~/components/ui/select'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '~/components/ui/table'; +import { TransactionType } from '@prisma/client'; +import { CheckSquareIcon, SquareIcon } from 'lucide-react'; +import type { RouterOutputs } from '~/trpc/shared'; +import currencySymbolMap from 'currency-symbol-map/map'; + +type Transaction = { + amount: number; + checked: boolean; + date: Date; + description: string; + tags: string; + type: TransactionType; +}; + +export function FileProcessing({ user }: { user: RouterOutputs['users']['get'] }) { + const fileInput = useRef(null); + const [stage, setStage] = useState<'select-file' | 'select-columns' | 'edit-rows'>('select-file'); // 'select-file' | 'select-columns + const [columns, setColumns] = useState([]); + const [records, setRecords] = useState[]>([]); + const [transactions, setTransactions] = useState([]); + const [dateColumn, setDateColumn] = useState(); + const [amountColumn, setAmountColumn] = useState(); + const [descriptionColumn, setDescriptionColumn] = useState(); + const [globalChecked, setGlobalChecked] = useState(true); + + const currencyFormatter = new Intl.NumberFormat('es-ES', { style: 'currency', currency: user.currency ?? 'EUR' }); + + return ( + <> + From CSV file + + {stage === 'select-file' ? ( + <> + + { + const file = fileInput.current?.files?.[0]; + if (!file) { + console.error('No file selected'); + return; + } + + const records = await getRecords(await file.text()); + setRecords(records); + setColumns(getColumns(records)); + setStage('select-columns'); + }} + /> + + ) : null} + {stage === 'select-columns' ? ( +
+
+

The date is at:

+ +
+
+

The amount is at:

+ +
+
+

The description is at:

+ +
+ +
+ ) : null} + {stage === 'edit-rows' ? ( + <> + + + + { + setTransactions((transactions) => { + const res = [...transactions]; + for (const t of res) { + t.checked = !globalChecked; + } + return res; + }); + setGlobalChecked(!globalChecked); + }} + className="cursor-pointer" + > + {globalChecked ? : } + + Date + Amount + Type + Description + Tags + + + + {transactions.map((transaction, i) => ( + + { + setTransactions((transactions) => { + const res = [...transactions]; + const t = res[i]; + if (!t) return res; + res[i] = { ...t, checked: !t.checked }; + return res; + }); + }} + > + {transaction.checked ? : } + + {format(transaction.date, 'yyyy-MM-dd')} + + {currencyFormatter.format(transaction.amount)} + + { + setTransactions((transactions) => { + const res = [...transactions]; + const t = res[i]; + if (!t) return res; + res[i] = { + ...t, + type: + res[i]?.type === TransactionType.EXPENSE + ? TransactionType.INCOME + : TransactionType.EXPENSE, + }; + return res; + }); + }} + > + {transaction.type} + + + e.target.select()} + defaultValue={transaction.description} + onChange={(event) => { + setTransactions((transactions) => { + const res = [...transactions]; + const t = res[i]; + if (!t) return res; + res[i] = { ...t, description: event.target.value }; + return res; + }); + }} + /> + + + e.target.select()} + defaultValue={transaction.tags} + onChange={(event) => { + setTransactions((transactions) => { + const res = [...transactions]; + const t = res[i]; + if (!t) return res; + res[i] = { ...t, tags: event.target.value }; + return res; + }); + }} + /> + + + ))} + +
+ + + ) : null} +
+ + ); +} + +function getRecords(text: string): Promise[]> { + return new Promise((res, rej) => { + parseCSV(text, { delimiter: ';', columns: true }, (err, records) => { + if (err) return rej(err); + res(records as Record[]); + }); + }); +} + +function getColumns(records: Record[]): string[] { + const columns = new Set(); + for (const record of records) { + for (const column of Object.keys(record)) { + columns.add(column); + } + } + + return [...columns]; +} diff --git a/src/app/dashboard/file/page.tsx b/src/app/dashboard/file/page.tsx index 2509747..7d4c14a 100644 --- a/src/app/dashboard/file/page.tsx +++ b/src/app/dashboard/file/page.tsx @@ -1,275 +1,7 @@ -'use client'; +import { api } from '~/trpc/server'; +import { FileProcessing } from '~/app/dashboard/file/file-processing'; -import { format, parse as parseDate } from 'date-fns'; -import { useRef, useState } from 'react'; -import { BlockTitle, BlockBody } from '~/app/_components/block'; -import { Input } from '~/components/ui/input'; -import { Label } from '~/components/ui/label'; -import { parse as parseCSV } from 'csv-parse'; -import { cn } from '~/lib/utils.client'; -import { Button } from '~/components/ui/button'; -import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '~/components/ui/select'; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '~/components/ui/table'; -import { TransactionType } from '@prisma/client'; -import { CheckSquareIcon, SquareIcon } from 'lucide-react'; - -type Transaction = { - amount: number; - checked: boolean; - date: Date; - description: string; - tags: string; - type: TransactionType; -}; - -export default function FilePage() { - const fileInput = useRef(null); - const [stage, setStage] = useState<'select-file' | 'select-columns' | 'edit-rows'>('select-file'); // 'select-file' | 'select-columns - const [columns, setColumns] = useState([]); - const [records, setRecords] = useState[]>([]); - const [transactions, setTransactions] = useState([]); - const [dateColumn, setDateColumn] = useState(); - const [amountColumn, setAmountColumn] = useState(); - const [descriptionColumn, setDescriptionColumn] = useState(); - const [globalChecked, setGlobalChecked] = useState(true); - - return ( - <> - From CSV file - - {stage === 'select-file' ? ( - <> - - { - const file = fileInput.current?.files?.[0]; - if (!file) { - console.error('No file selected'); - return; - } - - const records = await getRecords(await file.text()); - setRecords(records); - setColumns(getColumns(records)); - setStage('select-columns'); - }} - /> - - ) : null} - {stage === 'select-columns' ? ( -
-
-

The date is at:

- -
-
-

The amount is at:

- -
-
-

The description is at:

- -
- -
- ) : null} - {stage === 'edit-rows' ? ( - <> - - - - { - setTransactions((transactions) => { - const res = [...transactions]; - for (const t of res) { - t.checked = !globalChecked; - } - return res; - }); - setGlobalChecked(!globalChecked); - }} - className="cursor-pointer" - > - {globalChecked ? : } - - Date - Amount - Type - Description - Tags - - - - {transactions.map((transaction, i) => ( - - { - setTransactions((transactions) => { - const res = [...transactions]; - const t = res[i]; - if (!t) return res; - res[i] = { ...t, checked: !t.checked }; - return res; - }); - }} - > - {transaction.checked ? : } - - {format(transaction.date, 'yyyy-MM-dd')} - {transaction.amount} - { - setTransactions((transactions) => { - const res = [...transactions]; - const t = res[i]; - if (!t) return res; - res[i] = { - ...t, - type: - res[i]?.type === TransactionType.EXPENSE - ? TransactionType.INCOME - : TransactionType.EXPENSE, - }; - return res; - }); - }} - > - {transaction.type} - - - e.target.select()} - defaultValue={transaction.description} - onChange={(event) => { - setTransactions((transactions) => { - const res = [...transactions]; - const t = res[i]; - if (!t) return res; - res[i] = { ...t, description: event.target.value }; - return res; - }); - }} - /> - - - e.target.select()} - defaultValue={transaction.tags} - onChange={(event) => { - setTransactions((transactions) => { - const res = [...transactions]; - const t = res[i]; - if (!t) return res; - res[i] = { ...t, tags: event.target.value }; - return res; - }); - }} - /> - - - ))} - -
- - - ) : null} -
- - ); -} - -function getRecords(text: string): Promise[]> { - return new Promise((res, rej) => { - parseCSV(text, { delimiter: ';', columns: true }, (err, records) => { - if (err) return rej(err); - res(records as Record[]); - }); - }); -} - -function getColumns(records: Record[]): string[] { - const columns = new Set(); - for (const record of records) { - for (const column of Object.keys(record)) { - columns.add(column); - } - } - - return [...columns]; +export default async function FilePage() { + const user = await api.users.get.query(); + return ; }