feat: add ReconcileDateRangeCard to settings page; enhance date picker layout in statistics and transaction filters components
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 1m24s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 1m24s
This commit is contained in:
69
app/api/banking/transactions/reconcile-date-range/route.ts
Normal file
69
app/api/banking/transactions/reconcile-date-range/route.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { transactionService } from "@/services/transaction.service";
|
||||
import { requireAuth } from "@/lib/auth-utils";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const authError = await requireAuth();
|
||||
if (authError) return authError;
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { startDate, endDate, reconciled = true } = body;
|
||||
|
||||
if (!endDate) {
|
||||
return NextResponse.json(
|
||||
{ error: "endDate is required" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// Validate date format (YYYY-MM-DD)
|
||||
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
||||
if (startDate && !dateRegex.test(startDate)) {
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid startDate format. Expected YYYY-MM-DD" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
if (!dateRegex.test(endDate)) {
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid endDate format. Expected YYYY-MM-DD" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
if (startDate && startDate > endDate) {
|
||||
return NextResponse.json(
|
||||
{ error: "startDate must be before or equal to endDate" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const result = await transactionService.reconcileByDateRange(
|
||||
startDate,
|
||||
endDate,
|
||||
reconciled,
|
||||
);
|
||||
|
||||
// Revalider le cache des pages
|
||||
revalidatePath("/transactions", "page");
|
||||
revalidatePath("/statistics", "page");
|
||||
revalidatePath("/dashboard", "page");
|
||||
revalidatePath("/settings", "page");
|
||||
|
||||
return NextResponse.json(result, {
|
||||
headers: {
|
||||
"Cache-Control": "no-store",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error reconciling transactions by date range:", error);
|
||||
const errorMessage =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to reconcile transactions";
|
||||
return NextResponse.json({ error: errorMessage }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
OFXInfoCard,
|
||||
BackupCard,
|
||||
PasswordCard,
|
||||
ReconcileDateRangeCard,
|
||||
} from "@/components/settings";
|
||||
import { useBankingData } from "@/lib/hooks";
|
||||
import type { BankingData } from "@/lib/types";
|
||||
@@ -125,6 +126,8 @@ export default function SettingsPage() {
|
||||
|
||||
<PasswordCard />
|
||||
|
||||
<ReconcileDateRangeCard />
|
||||
|
||||
<DangerZoneCard
|
||||
categorizedCount={categorizedCount}
|
||||
onClearCategories={clearAllCategories}
|
||||
|
||||
@@ -950,52 +950,57 @@ export default function StatisticsPage() {
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="p-3 flex gap-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
Date de début
|
||||
</label>
|
||||
<CalendarComponent
|
||||
mode="single"
|
||||
selected={customStartDate}
|
||||
onSelect={(date) => {
|
||||
setCustomStartDate(date);
|
||||
if (date && customEndDate && date > customEndDate) {
|
||||
setCustomEndDate(undefined);
|
||||
}
|
||||
}}
|
||||
locale={fr}
|
||||
/>
|
||||
<div className="scale-90 origin-top-left">
|
||||
<CalendarComponent
|
||||
mode="single"
|
||||
selected={customStartDate}
|
||||
onSelect={(date) => {
|
||||
setCustomStartDate(date);
|
||||
if (date && customEndDate && date > customEndDate) {
|
||||
setCustomEndDate(undefined);
|
||||
}
|
||||
}}
|
||||
locale={fr}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
Date de fin
|
||||
</label>
|
||||
<CalendarComponent
|
||||
mode="single"
|
||||
selected={customEndDate}
|
||||
onSelect={(date) => {
|
||||
if (
|
||||
date &&
|
||||
customStartDate &&
|
||||
date < customStartDate
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setCustomEndDate(date);
|
||||
if (date && customStartDate) {
|
||||
setIsCustomDatePickerOpen(false);
|
||||
}
|
||||
}}
|
||||
disabled={(date) => {
|
||||
if (!customStartDate) return true;
|
||||
return date < customStartDate;
|
||||
}}
|
||||
locale={fr}
|
||||
/>
|
||||
<div className="scale-90 origin-top-left">
|
||||
<CalendarComponent
|
||||
mode="single"
|
||||
selected={customEndDate}
|
||||
onSelect={(date) => {
|
||||
if (
|
||||
date &&
|
||||
customStartDate &&
|
||||
date < customStartDate
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setCustomEndDate(date);
|
||||
if (date && customStartDate) {
|
||||
setIsCustomDatePickerOpen(false);
|
||||
}
|
||||
}}
|
||||
disabled={(date) => {
|
||||
if (!customStartDate) return true;
|
||||
return date < customStartDate;
|
||||
}}
|
||||
locale={fr}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{customStartDate && customEndDate && (
|
||||
<div className="flex gap-2 pt-2 border-t">
|
||||
</div>
|
||||
{customStartDate && customEndDate && (
|
||||
<div className="flex gap-2 pt-2 border-t px-3 pb-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -1016,7 +1021,6 @@ export default function StatisticsPage() {
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user