From 4f945632cba6f8226381a00a58fef1f77132ad8f Mon Sep 17 00:00:00 2001
From: Oscar Cedano
Date: Sat, 7 Nov 2026 03:18:30 -0600
Subject: [PATCH 1/6] =?UTF-8?q?=E2=9C=85=20feat:=20open=20party=20reports?=
=?UTF-8?q?=20inline=20with=20placeholder=20detail=20view?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-Authored-By: Cursor Composer 2.5
---
src/presentation/pages/report/Report.tsx | 21 ++++--
.../components/ReportDetailPlaceholder.tsx | 42 ++++++++++++
.../report/components/ReportsTab.test.tsx | 64 ++++++++++++++++++-
.../pages/report/components/ReportsTab.tsx | 37 ++++++++++-
.../shared/components/PartyReportCard.tsx | 48 ++++++++++----
5 files changed, 190 insertions(+), 22 deletions(-)
create mode 100644 src/presentation/pages/report/components/ReportDetailPlaceholder.tsx
diff --git a/src/presentation/pages/report/Report.tsx b/src/presentation/pages/report/Report.tsx
index 0c53956..0e988cd 100644
--- a/src/presentation/pages/report/Report.tsx
+++ b/src/presentation/pages/report/Report.tsx
@@ -85,6 +85,7 @@ function Report({ type }: { type: "scammer" | "organization" }) {
useDependencies();
const profileKey = `${type}:${id ?? ""}`;
const [activeTab, setActiveTab] = useState("General");
+ const [viewingReportId, setViewingReportId] = useState(null);
const [activeProfileKey, setActiveProfileKey] = useState(profileKey);
const [profilePictureOverride, setProfilePictureOverride] =
useState(null);
@@ -100,8 +101,14 @@ function Report({ type }: { type: "scammer" | "organization" }) {
setActiveProfileKey(profileKey);
setActiveTab("General");
setProfilePictureOverride(null);
+ setViewingReportId(null);
}
+ const handleTabChange = (tab: ReportTab) => {
+ setActiveTab(tab);
+ setViewingReportId(null);
+ };
+
useLayoutEffect(() => {
scrollViewToTop();
}, [profileKey]);
@@ -206,7 +213,7 @@ function Report({ type }: { type: "scammer" | "organization" }) {
)}
@@ -262,11 +269,17 @@ function Report({ type }: { type: "scammer" | "organization" }) {
partyId={party.id}
partyType={type}
reports={party.reports}
- onNavigateTab={setActiveTab}
+ onNavigateTab={handleTabChange}
/>
)}
{activeTab === "Reportes" && (
-
+ setViewingReportId(null)}
+ />
)}
{activeTab === "Contactos" && (
@@ -274,7 +287,7 @@ function Report({ type }: { type: "scammer" | "organization" }) {
{activeTab === "Métodos de pago" && (
)}
- {(activeTab === "Empresas" || activeTab === "Estafadores") && (
+ {(activeTab === "Empresas" || activeTab === "Colaboradores") && (
+
+
+ );
+}
+
+type ReportDetailPlaceholderProps = {
+ onBack: () => void;
+};
+
+function ReportDetailPlaceholder({ onBack }: ReportDetailPlaceholderProps) {
+ return (
+
+
+
+ This is the report content
+
+
+ );
+}
+
+export default ReportDetailPlaceholder;
diff --git a/src/presentation/pages/report/components/ReportsTab.test.tsx b/src/presentation/pages/report/components/ReportsTab.test.tsx
index d63f149..4a72eb2 100644
--- a/src/presentation/pages/report/components/ReportsTab.test.tsx
+++ b/src/presentation/pages/report/components/ReportsTab.test.tsx
@@ -51,7 +51,7 @@ describe("ReportsTab", () => {
expect(execute).toHaveBeenCalledWith("20", "scammer", 1);
expect(screen.getByText("Historial de Reportes:")).toBeInTheDocument();
- expect(screen.getByRole("button", { name: "Agregar +" })).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: "Agregar Reporte +" })).toBeInTheDocument();
expect(screen.getByText("#1 - Título del reporte 1-0")).toBeInTheDocument();
expect(
screen.getByRole("link", { name: /Título del reporte 1-0/ }),
@@ -82,7 +82,7 @@ describe("ReportsTab", () => {
expect(screen.getByRole("button", { name: "Página 2" })).toBeInTheDocument();
});
- expect(screen.getByRole("button", { name: "Agregar +" })).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: "Agregar Reporte +" })).toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "Página 2" }));
@@ -159,4 +159,64 @@ describe("ReportsTab", () => {
expect(execute).toHaveBeenCalledTimes(2);
});
+
+ it("calls onSelectReport when a report card is clicked", async () => {
+ const execute = vi.fn().mockResolvedValue(createPage(1));
+ const onSelectReport = vi.fn();
+ const user = userEvent.setup();
+
+ renderWithProviders(
+ ,
+ {
+ overrides: {
+ findReportsByPartyUseCase: {
+ execute,
+ cancel: vi.fn(),
+ },
+ },
+ },
+ );
+
+ await waitFor(() => {
+ expect(screen.getAllByRole("article")).toHaveLength(10);
+ });
+
+ await user.click(
+ screen.getByRole("button", { name: /Ver reporte Título del reporte 1-0/ }),
+ );
+
+ expect(onSelectReport).toHaveBeenCalledWith("1");
+ });
+
+ it("shows placeholder content when a report is selected", () => {
+ const execute = vi.fn().mockResolvedValue(createPage(1));
+
+ renderWithProviders(
+ ,
+ {
+ overrides: {
+ findReportsByPartyUseCase: {
+ execute,
+ cancel: vi.fn(),
+ },
+ },
+ },
+ );
+
+ expect(screen.getByText("This is the report content")).toBeInTheDocument();
+ expect(
+ screen.getByRole("button", { name: /Volver al listado/ }),
+ ).toBeInTheDocument();
+ expect(screen.queryByText("Historial de Reportes:")).not.toBeInTheDocument();
+ });
});
diff --git a/src/presentation/pages/report/components/ReportsTab.tsx b/src/presentation/pages/report/components/ReportsTab.tsx
index 614e890..838f586 100644
--- a/src/presentation/pages/report/components/ReportsTab.tsx
+++ b/src/presentation/pages/report/components/ReportsTab.tsx
@@ -5,6 +5,7 @@ import { useDependencies } from "@/presentation/providers/useDependencies";
import AddReportModal, {
type AddReportDraft,
} from "@presentation/pages/report/components/AddReportModal";
+import ReportDetailPlaceholder from "@presentation/pages/report/components/ReportDetailPlaceholder";
import PartyReportCard from "@/presentation/shared/components/PartyReportCard";
import { isCanceledError } from "@/common/utils/http-error.util";
import PaginationNav from "@/presentation/shared/components/PaginationNav";
@@ -12,6 +13,9 @@ import PaginationNav from "@/presentation/shared/components/PaginationNav";
interface ReportsTabProps {
partyId: string;
partyType: "scammer" | "organization";
+ selectedReportId?: string | null;
+ onSelectReport?: (reportId: string) => void;
+ onBackFromReport?: () => void;
}
function toAttachedReportRequest(
@@ -26,7 +30,13 @@ function toAttachedReportRequest(
};
}
-function ReportsTab({ partyId, partyType }: ReportsTabProps) {
+function ReportsTab({
+ partyId,
+ partyType,
+ selectedReportId = null,
+ onSelectReport,
+ onBackFromReport,
+}: ReportsTabProps) {
const {
findReportsByPartyUseCase,
createReportForScammerUseCase,
@@ -50,6 +60,10 @@ function ReportsTab({ partyId, partyType }: ReportsTabProps) {
const isLoading = loadedRequestKey !== requestKey;
useEffect(() => {
+ if (selectedReportId) {
+ return;
+ }
+
let ignore = false;
findReportsByPartyUseCase
@@ -91,6 +105,7 @@ function ReportsTab({ partyId, partyType }: ReportsTabProps) {
partyType,
requestKey,
requestVersion,
+ selectedReportId,
]);
useEffect(() => {
@@ -154,6 +169,14 @@ function ReportsTab({ partyId, partyType }: ReportsTabProps) {
}
}
+ if (selectedReportId) {
+ return (
+ onBackFromReport?.()}
+ />
+ );
+ }
+
return (
@@ -165,7 +188,7 @@ function ReportsTab({ partyId, partyType }: ReportsTabProps) {
onClick={() => setIsAddReportOpen(true)}
className="cursor-pointer rounded-md bg-red-600 px-4 py-2 text-sm font-extrabold text-white hover:bg-red-700"
>
- Agregar +
+ Agregar Reporte +
@@ -179,7 +202,15 @@ function ReportsTab({ partyId, partyType }: ReportsTabProps) {
{!isLoading &&
!errorMessage &&
reports.map((report) => (
-
+ onSelectReport(selected.id)
+ : undefined
+ }
+ />
))}
{!isLoading && errorMessage && (
diff --git a/src/presentation/shared/components/PartyReportCard.tsx b/src/presentation/shared/components/PartyReportCard.tsx
index 43033b1..d7583cb 100644
--- a/src/presentation/shared/components/PartyReportCard.tsx
+++ b/src/presentation/shared/components/PartyReportCard.tsx
@@ -28,13 +28,14 @@ function ExternalLinkIcon() {
);
}
-function PartyReportCard({ report }: { report: PartyReportEntity }) {
- return (
-
-
+interface PartyReportCardProps {
+ report: PartyReportEntity;
+ onSelect?: (report: PartyReportEntity) => void;
+}
+
+function PartyReportCard({ report, onSelect }: PartyReportCardProps) {
+ const cardBody = (
+ <>
#{report.id} - {report.title}
@@ -44,13 +45,34 @@ function PartyReportCard({ report }: { report: PartyReportEntity }) {
-
+ {report.description}
+
+ >
+ );
+
+ const cardClassName =
+ "block w-full cursor-pointer rounded-md border border-gray-200 px-4 py-3 text-left hover:bg-gray-50";
+
+ return (
+
+ {onSelect ? (
+
-
+ {cardBody}
+
+ ) : (
+
+ {cardBody}
+
+ )}
);
}
From a3c49fe6fc2fec7464d79c4f30e60544ac95041d Mon Sep 17 00:00:00 2001
From: Oscar Cedano
Date: Sat, 7 Nov 2026 03:18:33 -0600
Subject: [PATCH 2/6] =?UTF-8?q?=E2=9C=85=20feat:=20align=20Colaboradores?=
=?UTF-8?q?=20and=20Empresa=20copy=20across=20associations=20UI?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-Authored-By: Cursor Composer 2.5
---
.../components/CollaboratorFormModal.tsx | 10 +++++-----
src/presentation/pages/report/Report.test.tsx | 2 +-
.../report/components/AssociationCard.tsx | 18 ++++++++++++++----
.../report/components/AssociationsTab.tsx | 15 +++++++++++----
.../report/components/ReportTabNavigation.tsx | 2 +-
.../pages/report/components/types.ts | 2 +-
.../shared/components/AddOrganizationModal.tsx | 4 +++-
7 files changed, 36 insertions(+), 17 deletions(-)
diff --git a/src/presentation/pages/report-form/components/CollaboratorFormModal.tsx b/src/presentation/pages/report-form/components/CollaboratorFormModal.tsx
index fe4f28c..2744acf 100644
--- a/src/presentation/pages/report-form/components/CollaboratorFormModal.tsx
+++ b/src/presentation/pages/report-form/components/CollaboratorFormModal.tsx
@@ -72,14 +72,14 @@ const FORM_COPY: Record<
"Los perfiles/números que haya utilizado el colaborador para contactarte",
},
organization: {
- photoAdd: "Agregar foto de la organización",
- photoChange: "Cambiar foto de la organización",
- name: "Nombre de la organización",
+ photoAdd: "Agregar foto de la empresa",
+ photoChange: "Cambiar foto de la empresa",
+ name: "Nombre de la empresa",
placeholder: "Ej. (Billions Trade Club, Globoshop)",
payments:
- "Los números de cuenta/bancos/wallets que esté utilizando la organización para captar fondos",
+ "Los números de cuenta/bancos/wallets que esté utilizando la empresa para captar fondos",
contacts:
- "Los perfiles/números que haya utilizado la organización para contactarte",
+ "Los perfiles/números que haya utilizado la empresa para contactarte",
},
};
diff --git a/src/presentation/pages/report/Report.test.tsx b/src/presentation/pages/report/Report.test.tsx
index 38de5c6..b018693 100644
--- a/src/presentation/pages/report/Report.test.tsx
+++ b/src/presentation/pages/report/Report.test.tsx
@@ -320,7 +320,7 @@ describe("Report page", () => {
,
);
- await user.click(await screen.findByRole("tab", { name: "Estafadores" }));
+ await user.click(await screen.findByRole("tab", { name: "Colaboradores" }));
expect(
await screen.findByRole("link", { name: /Ada Lovelace/ }),
).toBeInTheDocument();
diff --git a/src/presentation/pages/report/components/AssociationCard.tsx b/src/presentation/pages/report/components/AssociationCard.tsx
index a268c47..8080669 100644
--- a/src/presentation/pages/report/components/AssociationCard.tsx
+++ b/src/presentation/pages/report/components/AssociationCard.tsx
@@ -3,7 +3,7 @@ import { organizationPath, scammerPath } from "@/common/app-routes";
import PartyAssociationEntity from "@/core/domain/association/entities/party-association.entity";
import { formatContactDate } from "@presentation/pages/report/components/contact-date.util";
-function ArrowIcon() {
+function ExternalLinkIcon() {
return (