Files
sapiens-web3/client/src/pages/Report.tsx
kimjaehyeon0101 0894a95fa9 Improve the visual appearance of reports with enhanced styling
Update the Report page to apply custom CSS to iframe content for a more aesthetically pleasing presentation.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 0fb68265-c270-4198-9584-3d9be9bddb41
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3df548ff-50ae-432f-9be4-25d34eccc983/0fb68265-c270-4198-9584-3d9be9bddb41/g8mUCzT
2025-09-30 07:54:26 +00:00

459 lines
16 KiB
TypeScript

import { useRoute, useLocation } from "wouter";
import { FileText, Presentation, Info, Search, Settings, User, LogOut } from "lucide-react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { useQuery } from "@tanstack/react-query";
import { useAuth } from "@/hooks/useAuth";
import { useState } from "react";
import type { MediaOutlet } from "@shared/schema";
import Footer from "@/components/Footer";
import SearchModal from "@/components/SearchModal";
const reportContent: Record<string, { htmlPath: string; pptPath: string }> = {
'chayan-asli': {
htmlPath: '/attached_assets/chayan asli report_1759208054055.html',
pptPath: '/attached_assets/chayan asli slides_1759213492580.pptx'
},
'krysh-parker': {
htmlPath: '/attached_assets/krysh parker report_1759209671294.html',
pptPath: '/attached_assets/krysh parker slides_1759209102236.pptx'
}
};
export default function Report() {
const [, params] = useRoute("/media/:slug/report");
const [, setLocation] = useLocation();
const [enlargedImage, setEnlargedImage] = useState<string | null>(null);
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false);
const { user, isAuthenticated } = useAuth();
const slug = params?.slug || '';
const content = reportContent[slug] || reportContent['chayan-asli'];
const { data: outlet, isLoading: outletLoading } = useQuery<MediaOutlet>({
queryKey: ["/api/media-outlets", slug],
enabled: !!slug
});
const handleLogout = async () => {
try {
const response = await fetch("/api/logout", {
method: "POST",
credentials: "include",
});
if (response.ok) {
window.location.href = "/";
}
} catch (error) {
console.error("Logout failed:", error);
}
};
const handleAdminPage = () => {
if (user?.role === "admin" || user?.role === "superadmin") {
setLocation("/admin");
}
};
// Get full URL for PPT file
const getFullPptUrl = () => {
const baseUrl = window.location.origin;
return `${baseUrl}${content.pptPath}`;
};
// Enhance report styling
const handleReportLoad = (e: React.SyntheticEvent<HTMLIFrameElement>) => {
try {
const iframe = e.currentTarget;
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
if (iframeDoc && iframeDoc.body) {
// Wrap all body content in a container if not already wrapped
if (!iframeDoc.querySelector('.sapiens-report-wrapper')) {
const wrapper = iframeDoc.createElement('div');
wrapper.className = 'sapiens-report-wrapper';
while (iframeDoc.body.firstChild) {
wrapper.appendChild(iframeDoc.body.firstChild);
}
iframeDoc.body.appendChild(wrapper);
}
const style = iframeDoc.createElement('style');
style.textContent = `
html, body {
margin: 0 !important;
padding: 0 !important;
background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%) !important;
max-width: none !important;
width: 100% !important;
}
body {
padding: 48px 32px !important;
}
.sapiens-report-wrapper {
background: white !important;
border-radius: 16px !important;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1) !important;
padding: 56px !important;
max-width: 1000px !important;
margin: 0 auto !important;
}
h1 {
font-size: 36px !important;
font-weight: 700 !important;
color: #1a202c !important;
margin-bottom: 28px !important;
margin-top: 0 !important;
padding-bottom: 20px !important;
border-bottom: 3px solid #3b82f6 !important;
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important;
-webkit-background-clip: text !important;
-webkit-text-fill-color: transparent !important;
background-clip: text !important;
}
h2 {
font-size: 26px !important;
font-weight: 600 !important;
color: #2d3748 !important;
margin-top: 44px !important;
margin-bottom: 18px !important;
padding-bottom: 10px !important;
border-bottom: 2px solid #e2e8f0 !important;
}
h3 {
font-size: 22px !important;
font-weight: 600 !important;
color: #374151 !important;
margin-top: 32px !important;
margin-bottom: 14px !important;
}
h4 {
font-size: 19px !important;
font-weight: 600 !important;
color: #4b5563 !important;
margin-top: 26px !important;
margin-bottom: 12px !important;
}
p {
font-size: 17px !important;
line-height: 1.8 !important;
color: #374151 !important;
margin-bottom: 18px !important;
text-align: justify !important;
}
.metadata {
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%) !important;
padding: 24px !important;
border-radius: 12px !important;
border-left: 4px solid #3b82f6 !important;
margin-bottom: 36px !important;
font-size: 15px !important;
color: #1e40af !important;
text-align: center !important;
}
.section {
margin-bottom: 36px !important;
}
ul, ol {
margin-bottom: 20px !important;
padding-left: 32px !important;
}
li {
font-size: 17px !important;
line-height: 1.8 !important;
color: #4b5563 !important;
margin-bottom: 10px !important;
}
.highlight {
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%) !important;
padding: 24px !important;
border-left: 4px solid #f59e0b !important;
border-radius: 10px !important;
margin: 28px 0 !important;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.08) !important;
}
.quote {
background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%) !important;
padding: 24px 28px !important;
border-left: 4px solid #6b7280 !important;
border-radius: 10px !important;
margin: 28px 0 !important;
font-style: italic !important;
color: #374151 !important;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.08) !important;
}
strong {
font-weight: 700 !important;
color: #1f2937 !important;
}
img {
max-width: 100% !important;
height: auto !important;
border-radius: 12px !important;
box-shadow: 0 4px 12px -2px rgba(0, 0, 0, 0.15) !important;
margin: 20px 0 !important;
}
table {
max-width: 100% !important;
width: 100% !important;
border-collapse: collapse !important;
margin: 20px 0 !important;
}
table, iframe {
max-width: 100% !important;
}
@page {
margin: 0 !important;
}
@media (max-width: 768px) {
.sapiens-report-wrapper {
padding: 32px 24px !important;
}
h1 {
font-size: 28px !important;
}
h2 {
font-size: 22px !important;
}
h3 {
font-size: 19px !important;
}
}
`;
iframeDoc.head.appendChild(style);
}
} catch (e) {
console.error('Could not inject styles into iframe:', e);
}
};
return (
<div className="flex flex-col min-h-screen bg-gray-50">
{/* Header */}
<header className="bg-white border-b border-gray-200 sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
{outletLoading ? (
<>
<div className="w-10 h-10 bg-gray-200 rounded-full animate-pulse"></div>
<div className="flex flex-col mt-1">
<div className="h-2.5 w-16 bg-gray-200 rounded animate-pulse mb-0.5"></div>
<div className="h-5 w-24 bg-gray-200 rounded animate-pulse"></div>
</div>
</>
) : outlet ? (
<>
{outlet.imageUrl ? (
<img
src={outlet.imageUrl}
alt={outlet.name}
className="w-10 h-10 rounded-full object-cover cursor-pointer hover:ring-2 hover:ring-blue-400 transition-all"
onClick={() => setEnlargedImage(outlet.imageUrl!)}
data-testid="image-outlet-header-profile"
/>
) : (
<div className="w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center">
<span className="text-gray-600 font-bold text-sm">
{outlet.name.charAt(0)}
</span>
</div>
)}
<div className="flex flex-col cursor-pointer hover:opacity-80 transition-opacity mt-1" onClick={() => setLocation("/")}>
<img
src="/attached_assets/logo_black_1759162717640.png"
alt="SAPIENS"
className="h-2.5 w-auto max-w-[70px] mb-0.5"
data-testid="logo-sapiens"
/>
<div className="flex items-center space-x-1.5">
<span className="text-lg font-bold text-gray-900" data-testid="text-outlet-name-header">
{outlet.name}
</span>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setLocation(`/media/${outlet.slug}/report`);
}}
className="inline-flex items-center"
aria-label="View reports"
>
<Info
className="h-4 w-4 text-gray-500 cursor-pointer hover:text-gray-700"
data-testid="icon-info-header"
/>
</button>
</div>
</div>
</>
) : (
<img
src="/attached_assets/logo_black_1759162717640.png"
alt="SAPIENS"
className="h-6 w-auto cursor-pointer"
onClick={() => setLocation("/")}
data-testid="logo-sapiens"
/>
)}
</div>
<div className="flex items-center space-x-4">
<div
className="relative cursor-pointer"
onClick={() => setIsSearchModalOpen(true)}
data-testid="search-container"
>
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400 pointer-events-none" />
<Input
type="text"
placeholder="Search the website"
className="w-80 pl-10 bg-gray-50 border-gray-200 cursor-pointer"
data-testid="input-search"
readOnly
/>
</div>
{isAuthenticated && user ? (
<>
<Button
variant="ghost"
size="sm"
onClick={() => setLocation("/auctions")}
data-testid="button-auctions"
>
Auctions
</Button>
<Button
variant="ghost"
size="sm"
onClick={handleAdminPage}
data-testid="button-admin-dashboard"
>
Admin Dashboard
</Button>
<div className="flex items-center space-x-2 px-3 py-1 bg-gray-100 rounded-md">
<User className="h-4 w-4 text-gray-600" />
<span className="text-sm font-medium text-gray-700" data-testid="user-name">
{user.firstName} {user.lastName}
</span>
</div>
<Button
variant="ghost"
size="sm"
onClick={handleLogout}
data-testid="button-logout"
>
<LogOut className="h-4 w-4" />
</Button>
</>
) : (
<Button
variant="ghost"
size="sm"
onClick={() => setLocation("/login")}
data-testid="button-login"
>
Login
</Button>
)}
<Button
variant="ghost"
size="sm"
data-testid="button-settings"
>
<Settings className="h-4 w-4" />
</Button>
</div>
</div>
</div>
</header>
{/* Main Content */}
<main className="flex-1 max-w-[1600px] mx-auto px-8 py-2 pb-32 w-full">
<Tabs defaultValue="report" className="w-full">
<TabsList className="grid w-full max-w-md mx-auto grid-cols-2 mb-4">
<TabsTrigger value="report" data-testid="tab-report">
<FileText className="h-4 w-4 mr-2" />
Report
</TabsTrigger>
<TabsTrigger value="slides" data-testid="tab-slides">
<Presentation className="h-4 w-4 mr-2" />
Slides
</TabsTrigger>
</TabsList>
<TabsContent value="report">
<Card className="bg-white">
<CardContent className="p-0">
<iframe
src={content.htmlPath}
className="w-full border-none"
style={{ display: 'block', minHeight: '100vh', height: 'auto' }}
title="Comprehensive Report"
data-testid="iframe-report"
onLoad={handleReportLoad}
/>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="slides" className="w-full">
<div className="w-full bg-white rounded-lg overflow-hidden shadow-lg">
<div className="relative w-full" style={{ paddingTop: '56.25%' }}>
<iframe
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(getFullPptUrl())}`}
className="absolute top-0 left-0 w-full h-full border-none"
title="Presentation Slides"
data-testid="iframe-slides"
allowFullScreen
style={{
backgroundColor: 'white',
display: 'block'
}}
/>
</div>
</div>
</TabsContent>
</Tabs>
</main>
<Footer />
{/* Search Modal */}
<SearchModal
isOpen={isSearchModalOpen}
onClose={() => setIsSearchModalOpen(false)}
/>
</div>
);
}