mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 02:51:46 +00:00
fix(inbox): corrigir agrupamento de data por fuso de Brasilia
O Companion envia hora local com 'Z' literal (nao converte para UTC), entao o timestamp no DB ja carrega a data correta de Brasilia. Usava-se +3h no frontend, que deslocava a virada de dia para as 21h locais e fazia compras da tarde aparecerem como 'Ontem'. - getItemDateKey: remove offset (data UTC ja e a data de Brasilia) - getBrasiliaDateKey: usa UTC-3 apenas para calcular hoje/ontem - Paraleliza insercoes no batch endpoint com Promise.allSettled - Usa selectDistinct no fetchInboxSourceApps - Envolve InboxCard em memo e callbacks em useCallback Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -86,12 +86,10 @@ export async function POST(request: Request) {
|
||||
const body = await request.json();
|
||||
const { items } = inboxBatchSchema.parse(body);
|
||||
|
||||
// Processar cada item
|
||||
const results: BatchResult[] = [];
|
||||
|
||||
for (const item of items) {
|
||||
try {
|
||||
const [inserted] = await db
|
||||
// Processar todos os itens em paralelo
|
||||
const settled = await Promise.allSettled(
|
||||
items.map((item) =>
|
||||
db
|
||||
.insert(inboxItems)
|
||||
.values({
|
||||
userId: tokenRecord.userId,
|
||||
@@ -104,22 +102,26 @@ export async function POST(request: Request) {
|
||||
parsedAmount: item.parsedAmount?.toString(),
|
||||
status: "pending",
|
||||
})
|
||||
.returning({ id: inboxItems.id });
|
||||
.returning({ id: inboxItems.id }),
|
||||
),
|
||||
);
|
||||
|
||||
results.push({
|
||||
clientId: item.clientId,
|
||||
serverId: inserted.id,
|
||||
const results: BatchResult[] = settled.map((result, i) => {
|
||||
const item = items[i];
|
||||
if (result.status === "fulfilled") {
|
||||
return {
|
||||
clientId: item?.clientId,
|
||||
serverId: result.value[0]?.id,
|
||||
success: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[API] Error processing batch item:", error);
|
||||
results.push({
|
||||
clientId: item.clientId,
|
||||
success: false,
|
||||
error: "Erro ao processar notificação",
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
console.error("[API] Error processing batch item:", result.reason);
|
||||
return {
|
||||
clientId: item?.clientId,
|
||||
success: false,
|
||||
error: "Erro ao processar notificação",
|
||||
};
|
||||
});
|
||||
|
||||
// Atualizar último uso do token
|
||||
const clientIp =
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { format, formatDistanceToNow } from "date-fns";
|
||||
import { ptBR } from "date-fns/locale";
|
||||
import Image from "next/image";
|
||||
import { memo } from "react";
|
||||
import MoneyValues from "@/shared/components/money-values";
|
||||
import { Badge } from "@/shared/components/ui/badge";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
@@ -62,7 +63,7 @@ interface InboxCardProps {
|
||||
onSelectToggle?: (id: string) => void;
|
||||
}
|
||||
|
||||
export function InboxCard({
|
||||
export const InboxCard = memo(function InboxCard({
|
||||
item,
|
||||
readonly,
|
||||
appLogoMap,
|
||||
@@ -222,4 +223,4 @@ export function InboxCard({
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,7 +13,13 @@ import { format } from "date-fns";
|
||||
import { ptBR } from "date-fns/locale";
|
||||
import Image from "next/image";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { useEffect, useMemo, useState, useTransition } from "react";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
useTransition,
|
||||
} from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
bulkDeleteInboxItemsAction,
|
||||
@@ -57,18 +63,25 @@ import type {
|
||||
SelectOption,
|
||||
} from "./types";
|
||||
|
||||
const BRASILIA_OFFSET_MS = 3 * 60 * 60 * 1000;
|
||||
const DEFAULT_INBOX_APP_LOGO = "/avatars/default_icon.png";
|
||||
|
||||
function getDateKey(date: Date): string {
|
||||
const adjusted = new Date(date.getTime() + BRASILIA_OFFSET_MS);
|
||||
return adjusted.toISOString().slice(0, 10);
|
||||
// O Companion envia hora local de Brasília com 'Z' literal (não converte para UTC).
|
||||
// Por isso, o timestamp armazenado no DB já tem a data correta de Brasília como componente UTC.
|
||||
// Basta fatiar o ISO string sem nenhum ajuste para obter a data de Brasília do item.
|
||||
function getItemDateKey(date: Date): string {
|
||||
return date.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
// Para "hoje" e "ontem", precisamos da data real de Brasília (UTC-3).
|
||||
function getBrasiliaDateKey(date: Date): string {
|
||||
const BRASILIA_OFFSET_MS = 3 * 60 * 60 * 1000;
|
||||
return new Date(date.getTime() - BRASILIA_OFFSET_MS).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function getGroupLabel(dateKey: string): string {
|
||||
const now = new Date();
|
||||
const todayKey = getDateKey(now);
|
||||
const yesterdayKey = getDateKey(
|
||||
const todayKey = getBrasiliaDateKey(now);
|
||||
const yesterdayKey = getBrasiliaDateKey(
|
||||
new Date(now.getTime() - 24 * 60 * 60 * 1000),
|
||||
);
|
||||
if (dateKey === todayKey) return "Hoje";
|
||||
@@ -84,7 +97,7 @@ function groupItemsByDay(
|
||||
): { label: string; items: InboxItem[] }[] {
|
||||
const groups = new Map<string, InboxItem[]>();
|
||||
for (const item of items) {
|
||||
const key = getDateKey(new Date(item.notificationTimestamp));
|
||||
const key = getItemDateKey(new Date(item.notificationTimestamp));
|
||||
const group = groups.get(key);
|
||||
if (group) {
|
||||
group.push(item);
|
||||
@@ -234,20 +247,20 @@ export function InboxPage({
|
||||
}
|
||||
};
|
||||
|
||||
const handleProcessRequest = (item: InboxItem) => {
|
||||
const handleProcessRequest = useCallback((item: InboxItem) => {
|
||||
setItemToProcess(item);
|
||||
setProcessOpen(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleDetailsRequest = (item: InboxItem) => {
|
||||
const handleDetailsRequest = useCallback((item: InboxItem) => {
|
||||
setItemDetails(item);
|
||||
setDetailsOpen(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleDiscardRequest = (item: InboxItem) => {
|
||||
const handleDiscardRequest = useCallback((item: InboxItem) => {
|
||||
setItemToDiscard(item);
|
||||
setDiscardOpen(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleDiscardConfirm = async () => {
|
||||
if (!itemToDiscard) return;
|
||||
@@ -272,10 +285,10 @@ export function InboxPage({
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteRequest = (item: InboxItem) => {
|
||||
const handleDeleteRequest = useCallback((item: InboxItem) => {
|
||||
setItemToDelete(item);
|
||||
setDeleteOpen(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleDeleteConfirm = async () => {
|
||||
if (!itemToDelete) return;
|
||||
@@ -300,10 +313,10 @@ export function InboxPage({
|
||||
}
|
||||
};
|
||||
|
||||
const handleRestoreRequest = (item: InboxItem) => {
|
||||
const handleRestoreRequest = useCallback((item: InboxItem) => {
|
||||
setItemToRestore(item);
|
||||
setRestoreOpen(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleRestoreToPendingConfirm = async () => {
|
||||
if (!itemToRestore) return;
|
||||
@@ -326,13 +339,13 @@ export function InboxPage({
|
||||
setSelectedIds((current) => current.filter((id) => visibleIds.has(id)));
|
||||
}, [items]);
|
||||
|
||||
const toggleSelection = (id: string) => {
|
||||
const toggleSelection = useCallback((id: string) => {
|
||||
setSelectedIds((current) =>
|
||||
current.includes(id)
|
||||
? current.filter((value) => value !== id)
|
||||
: [...current, id],
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const allSelected = items.length > 0 && selectedIds.length === items.length;
|
||||
|
||||
|
||||
@@ -89,15 +89,14 @@ export async function fetchInboxSourceApps(
|
||||
status: InboxStatus,
|
||||
): Promise<string[]> {
|
||||
const rows = await db
|
||||
.select({ name: inboxItems.sourceAppName })
|
||||
.selectDistinct({ name: inboxItems.sourceAppName })
|
||||
.from(inboxItems)
|
||||
.where(and(eq(inboxItems.userId, userId), eq(inboxItems.status, status)));
|
||||
|
||||
const seen = new Set<string>();
|
||||
for (const row of rows) {
|
||||
if (row.name) seen.add(row.name);
|
||||
}
|
||||
return [...seen].sort();
|
||||
return rows
|
||||
.map((row) => row.name)
|
||||
.filter((name): name is string => name !== null)
|
||||
.sort();
|
||||
}
|
||||
|
||||
export async function fetchInboxStatusCounts(
|
||||
|
||||
Reference in New Issue
Block a user