mirror of
https://github.com/marcogll/TaxHacker_s23.git
synced 2026-01-13 21:35:19 +00:00
feat: bugfixes, spedup, bulk actions,
This commit is contained in:
@@ -5,9 +5,9 @@ import { WelcomeWidget } from "@/components/dashboard/welcome-widget"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { getUnsortedFiles } from "@/data/files"
|
||||
import { getSettings } from "@/data/settings"
|
||||
import { StatsFilters } from "@/data/stats"
|
||||
import { TransactionFilters } from "@/data/transactions"
|
||||
|
||||
export default async function Home({ searchParams }: { searchParams: Promise<StatsFilters> }) {
|
||||
export default async function Home({ searchParams }: { searchParams: Promise<TransactionFilters> }) {
|
||||
const filters = await searchParams
|
||||
const unsortedFiles = await getUnsortedFiles()
|
||||
const settings = await getSettings()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { addCategoryAction, deleteCategoryAction, editCategoryAction } from "@/app/settings/actions"
|
||||
import { CrudTable } from "@/components/settings/crud"
|
||||
import { getCategories } from "@/data/categories"
|
||||
import { randomHexColor } from "@/lib/utils"
|
||||
|
||||
export default async function CategoriesSettingsPage() {
|
||||
const categories = await getCategories()
|
||||
@@ -18,7 +19,7 @@ export default async function CategoriesSettingsPage() {
|
||||
columns={[
|
||||
{ key: "name", label: "Name", editable: true },
|
||||
{ key: "llm_prompt", label: "LLM Prompt", editable: true },
|
||||
{ key: "color", label: "Color", editable: true },
|
||||
{ key: "color", label: "Color", defaultValue: randomHexColor(), editable: true },
|
||||
]}
|
||||
onDelete={async (code) => {
|
||||
"use server"
|
||||
|
||||
@@ -16,7 +16,7 @@ export default async function CurrenciesSettingsPage() {
|
||||
<CrudTable
|
||||
items={currenciesWithActions}
|
||||
columns={[
|
||||
{ key: "code", label: "Code" },
|
||||
{ key: "code", label: "Code", editable: true },
|
||||
{ key: "name", label: "Name", editable: true },
|
||||
]}
|
||||
onDelete={async (code) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { addProjectAction, deleteProjectAction, editProjectAction } from "@/app/settings/actions"
|
||||
import { CrudTable } from "@/components/settings/crud"
|
||||
import { getProjects } from "@/data/projects"
|
||||
import { randomHexColor } from "@/lib/utils"
|
||||
|
||||
export default async function ProjectsSettingsPage() {
|
||||
const projects = await getProjects()
|
||||
@@ -18,7 +19,7 @@ export default async function ProjectsSettingsPage() {
|
||||
columns={[
|
||||
{ key: "name", label: "Name", editable: true },
|
||||
{ key: "llm_prompt", label: "LLM Prompt", editable: true },
|
||||
{ key: "color", label: "Color", editable: true },
|
||||
{ key: "color", label: "Color", defaultValue: randomHexColor(), editable: true },
|
||||
]}
|
||||
onDelete={async (code) => {
|
||||
"use server"
|
||||
|
||||
@@ -26,9 +26,9 @@ export default async function TransactionPage({ params }: { params: Promise<{ tr
|
||||
const projects = await getProjects()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className="flex flex-col md:flex-row flex-wrap justify-center items-start gap-10 p-5 bg-accent max-w-6xl">
|
||||
<div className="flex-1">
|
||||
<div className="flex flex-wrap flex-row items-start justify-center gap-4 max-w-6xl">
|
||||
<Card className="w-full flex-1 flex flex-col flex-wrap justify-center items-start gap-10 p-5 bg-accent">
|
||||
<div className="w-full">
|
||||
<TransactionEditForm
|
||||
transaction={transaction}
|
||||
categories={categories}
|
||||
@@ -37,26 +37,29 @@ export default async function TransactionPage({ params }: { params: Promise<{ tr
|
||||
fields={fields}
|
||||
projects={projects}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="max-w-[320px] space-y-4">
|
||||
<TransactionFiles transaction={transaction} files={files} />
|
||||
{transaction.text && (
|
||||
<details className="mt-10">
|
||||
<summary className="cursor-pointer text-sm font-medium">Recognized Text</summary>
|
||||
<Card className="flex items-stretch p-2 max-w-6xl">
|
||||
<div className="flex-1">
|
||||
<FormTextarea
|
||||
title=""
|
||||
name="text"
|
||||
defaultValue={transaction.text || ""}
|
||||
hideIfEmpty={true}
|
||||
className="w-full h-[400px]"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{transaction.text && (
|
||||
<Card className="flex items-stretch p-5 mt-10 max-w-6xl">
|
||||
<div className="flex-1">
|
||||
<FormTextarea
|
||||
title="Recognized Text"
|
||||
name="text"
|
||||
defaultValue={transaction.text || ""}
|
||||
hideIfEmpty={true}
|
||||
className="w-full h-[400px]"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</>
|
||||
<div className="w-1/3 max-w-[380px] space-y-4">
|
||||
<TransactionFiles transaction={transaction} files={files} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { createFile, deleteFile } from "@/data/files"
|
||||
import {
|
||||
bulkDeleteTransactions,
|
||||
createTransaction,
|
||||
deleteTransaction,
|
||||
getTransactionById,
|
||||
@@ -90,13 +91,13 @@ export async function deleteTransactionFileAction(
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
export async function uploadTransactionFileAction(formData: FormData): Promise<{ success: boolean; error?: string }> {
|
||||
export async function uploadTransactionFilesAction(formData: FormData): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
const transactionId = formData.get("transactionId") as string
|
||||
const file = formData.get("file") as File
|
||||
const files = formData.getAll("files") as File[]
|
||||
|
||||
if (!file || !transactionId) {
|
||||
return { success: false, error: "No file or transaction ID provided" }
|
||||
if (!files || !transactionId) {
|
||||
return { success: false, error: "No files or transaction ID provided" }
|
||||
}
|
||||
|
||||
const transaction = await getTransactionById(transactionId)
|
||||
@@ -109,29 +110,36 @@ export async function uploadTransactionFileAction(formData: FormData): Promise<{
|
||||
await mkdir(FILE_UPLOAD_PATH, { recursive: true })
|
||||
}
|
||||
|
||||
// Save file to filesystem
|
||||
const { fileUuid, filePath } = await getTransactionFileUploadPath(file.name, transaction)
|
||||
const arrayBuffer = await file.arrayBuffer()
|
||||
const buffer = Buffer.from(arrayBuffer)
|
||||
await writeFile(filePath, buffer)
|
||||
const fileRecords = await Promise.all(
|
||||
files.map(async (file) => {
|
||||
const { fileUuid, filePath } = await getTransactionFileUploadPath(file.name, transaction)
|
||||
const arrayBuffer = await file.arrayBuffer()
|
||||
const buffer = Buffer.from(arrayBuffer)
|
||||
await writeFile(filePath, buffer)
|
||||
|
||||
// Create file record in database
|
||||
const fileRecord = await createFile({
|
||||
id: fileUuid,
|
||||
filename: file.name,
|
||||
path: filePath,
|
||||
mimetype: file.type,
|
||||
isReviewed: true,
|
||||
metadata: {
|
||||
size: file.size,
|
||||
lastModified: file.lastModified,
|
||||
},
|
||||
})
|
||||
// Create file record in database
|
||||
const fileRecord = await createFile({
|
||||
id: fileUuid,
|
||||
filename: file.name,
|
||||
path: filePath,
|
||||
mimetype: file.type,
|
||||
isReviewed: true,
|
||||
metadata: {
|
||||
size: file.size,
|
||||
lastModified: file.lastModified,
|
||||
},
|
||||
})
|
||||
|
||||
return fileRecord
|
||||
})
|
||||
)
|
||||
|
||||
// Update invoice with the new file ID
|
||||
await updateTransactionFiles(
|
||||
transactionId,
|
||||
transaction.files ? [...(transaction.files as string[]), fileRecord.id] : [fileRecord.id]
|
||||
transaction.files
|
||||
? [...(transaction.files as string[]), ...fileRecords.map((file) => file.id)]
|
||||
: fileRecords.map((file) => file.id)
|
||||
)
|
||||
|
||||
revalidatePath(`/transactions/${transactionId}`)
|
||||
@@ -141,3 +149,14 @@ export async function uploadTransactionFileAction(formData: FormData): Promise<{
|
||||
return { success: false, error: `File upload failed: ${error}` }
|
||||
}
|
||||
}
|
||||
|
||||
export async function bulkDeleteTransactionsAction(transactionIds: string[]) {
|
||||
try {
|
||||
await bulkDeleteTransactions(transactionIds)
|
||||
revalidatePath("/transactions")
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error("Failed to delete transactions:", error)
|
||||
return { success: false, error: "Failed to delete transactions" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export default async function TransactionsPage({ searchParams }: { searchParams:
|
||||
<header className="flex flex-wrap items-center justify-between gap-2 mb-8">
|
||||
<h2 className="text-3xl font-bold tracking-tight">Transactions</h2>
|
||||
<div className="flex gap-2">
|
||||
<ExportTransactionsDialog filters={filters} fields={fields} categories={categories} projects={projects}>
|
||||
<ExportTransactionsDialog fields={fields} categories={categories} projects={projects}>
|
||||
<Button variant="outline">
|
||||
<Download />
|
||||
<span className="hidden md:block">Export</span>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import AnalyzeForm from "@/components/unsorted/analyze-form"
|
||||
import { FilePreview } from "@/components/files/preview"
|
||||
import { UploadButton } from "@/components/files/upload-button"
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card } from "@/components/ui/card"
|
||||
import AnalyzeForm from "@/components/unsorted/analyze-form"
|
||||
import { getCategories } from "@/data/categories"
|
||||
import { getCurrencies } from "@/data/currencies"
|
||||
import { getFields } from "@/data/fields"
|
||||
|
||||
Reference in New Issue
Block a user