Add media outlet header to comprehensive report view

Integrates the media outlet's header information, including its logo and name, into the Comprehensive Report page. The header is now sticky and displays dynamically based on the current report's media outlet. Includes new navigation and utility icons in the header, along with search modal functionality and user authentication hooks.

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/16cZmxV
This commit is contained in:
kimjaehyeon0101
2025-09-30 06:21:01 +00:00
parent bc18c2d658
commit ffae32d195
2 changed files with 190 additions and 13 deletions

View File

@ -54,13 +54,6 @@ export default function MainContent() {
return acc; return acc;
}, {} as Record<string, number>); }, {} as Record<string, number>);
console.log('📊 Article Count Debug:', {
totalArticles: allArticles.length,
totalOutlets: allOutlets.length,
articleCountByOutlet,
outletsWithArticles: Object.keys(articleCountByOutlet).length
});
// Group outlets by category and sort // Group outlets by category and sort
const getOutletsByCategory = (category: string) => { const getOutletsByCategory = (category: string) => {
const filtered = allOutlets.filter(outlet => const filtered = allOutlets.filter(outlet =>

View File

@ -1,8 +1,16 @@
import { useRoute } from "wouter"; import { useRoute, useLocation } from "wouter";
import { FileText, Presentation } from "lucide-react"; import { FileText, Presentation, Info, Search, Settings, User, LogOut } from "lucide-react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Card, CardContent } from "@/components/ui/card"; 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 Footer from "@/components/Footer";
import SearchModal from "@/components/SearchModal";
const reportContent: Record<string, { htmlPath: string; pptPath: string }> = { const reportContent: Record<string, { htmlPath: string; pptPath: string }> = {
'chayan-asli': { 'chayan-asli': {
@ -17,10 +25,39 @@ const reportContent: Record<string, { htmlPath: string; pptPath: string }> = {
export default function Report() { export default function Report() {
const [, params] = useRoute("/media/:slug/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 slug = params?.slug || '';
const content = reportContent[slug] || reportContent['chayan-asli']; 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 // Get full URL for PPT file
const getFullPptUrl = () => { const getFullPptUrl = () => {
const baseUrl = window.location.origin; const baseUrl = window.location.origin;
@ -63,10 +100,151 @@ export default function Report() {
return ( return (
<div className="flex flex-col min-h-screen bg-gray-50"> <div className="flex flex-col min-h-screen bg-gray-50">
{/* Header */} {/* Header */}
<header className="bg-white border-b border-gray-200 sticky top-0 z-10"> <header className="bg-white border-b border-gray-200 sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 py-4"> <div className="max-w-7xl mx-auto px-6 py-4">
<div className="flex items-center justify-center"> <div className="flex items-center justify-between">
<h1 className="text-2xl font-bold text-gray-900">Comprehensive Report</h1> <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">
<div className="h-3 w-20 bg-gray-200 rounded animate-pulse mb-1"></div>
<div className="h-3 w-16 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" onClick={() => setLocation("/")}>
<img
src="/attached_assets/logo_black_1759162717640.png"
alt="SAPIENS"
className="h-3 w-auto mb-0.5"
data-testid="logo-sapiens"
/>
<div className="flex items-center space-x-1.5">
<span className="text-sm font-semibold text-gray-900" data-testid="text-outlet-name-header">
{outlet.name}
</span>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={(e) => e.stopPropagation()}
className="inline-flex items-center"
aria-label="View outlet information"
>
<Info
className="h-3.5 w-3.5 text-gray-500 cursor-pointer hover:text-gray-700"
data-testid="icon-info-header"
/>
</button>
</TooltipTrigger>
<TooltipContent>
<p className="max-w-xs">{outlet.description || "Media Outlet"}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</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>
</div> </div>
</header> </header>
@ -121,6 +299,12 @@ export default function Report() {
</main> </main>
<Footer /> <Footer />
{/* Search Modal */}
<SearchModal
isOpen={isSearchModalOpen}
onClose={() => setIsSearchModalOpen(false)}
/>
</div> </div>
); );
} }